/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// r_material.cpp -- material script parsing and loading


#include "r_local.h"


#define MATERIALS_HASH_SIZE				MAX_MATERIALS / 4

typedef struct materialScript_s {
	char						name[MAX_OSPATH];

	char *						buffer;
	int							size;

	struct materialScript_s *	nextHash;
} materialScript_t;

static material_t				r_parseMaterial;
static stage_t					r_parseStages[MAX_STAGES];
static expOp_t					r_parseExpressionOps[MAX_EXPRESSION_OPS];
static float					r_parseExpressionRegisters[MAX_EXPRESSION_REGISTERS];
static int						r_parseTextureFlags[MAX_STAGES];
static textureFilter_t			r_parseTextureFilter[MAX_STAGES];
static textureWrap_t			r_parseTextureWrap[MAX_STAGES];

static materialScript_t *		r_materialScriptsHashTable[MATERIALS_HASH_SIZE];

static material_t *				r_materialsHashTable[MATERIALS_HASH_SIZE];
static material_t *				r_materials[MAX_MATERIALS];
static int						r_numMaterials;


/*
 ==============================================================================

 MATERIAL EXPRESSION PARSING

 ==============================================================================
*/

#define MAX_EXPRESSION_VALUES			64
#define MAX_EXPRESSION_OPERATORS		64


typedef struct {
	const char *				name;
	int							expressionRegister;
} expValueTable_t;

typedef struct expValue_s {
	int							expressionRegister;

	int							brackets;
	int							parentheses;

	struct expValue_s *			prev;
	struct expValue_s *			next;
} expValue_t;

typedef struct {
	const char *				name;
	opType_t					opType;
	int							priority;
} expOperatorTable_t;

typedef struct expOperator_s {
	opType_t					opType;
	int							priority;

	int							brackets;
	int							parentheses;

	struct expOperator_s *		prev;
	struct expOperator_s *		next;
} expOperator_t;

typedef struct {
	int							numValues;
	expValue_t					values[MAX_EXPRESSION_VALUES];
	expValue_t *				firstValue;
	expValue_t *				lastValue;

	int							numOperators;
	expOperator_t				operators[MAX_EXPRESSION_OPERATORS];
	expOperator_t *				firstOperator;
	expOperator_t *				lastOperator;

	int							brackets;
	int							parentheses[MAX_EXPRESSION_VALUES + MAX_EXPRESSION_OPERATORS];

	int							resultRegister;
} materialExpression_t;

static expValueTable_t			r_expValueTable[] = {
	{"shaderPrograms",				EXP_REGISTER_SHADERPROGRAMS},
	{"shaderLevel",					EXP_REGISTER_SHADERLEVEL},
	{"time",						EXP_REGISTER_TIME},
	{"parm0",						EXP_REGISTER_PARM0},
	{"parm1",						EXP_REGISTER_PARM1},
	{"parm2",						EXP_REGISTER_PARM2},
	{"parm3",						EXP_REGISTER_PARM3},
	{"parm4",						EXP_REGISTER_PARM4},
	{"parm5",						EXP_REGISTER_PARM5},
	{"parm6",						EXP_REGISTER_PARM6},
	{"parm7",						EXP_REGISTER_PARM7},
	{"global0",						EXP_REGISTER_GLOBAL0},
	{"global1",						EXP_REGISTER_GLOBAL1},
	{"global2",						EXP_REGISTER_GLOBAL2},
	{"global3",						EXP_REGISTER_GLOBAL3},
	{"global4",						EXP_REGISTER_GLOBAL4},
	{"global5",						EXP_REGISTER_GLOBAL5},
	{"global6",						EXP_REGISTER_GLOBAL6},
	{"global7",						EXP_REGISTER_GLOBAL7},
	{NULL,							0}
};

static expOperatorTable_t		r_expOperatorTable[] = {
	{"*",							OP_TYPE_MULTIPLY,	7},
	{"/",							OP_TYPE_DIVIDE,		7},
	{"%",							OP_TYPE_MOD,		6},
	{"+",							OP_TYPE_ADD,		5},
	{"-",							OP_TYPE_SUBTRACT,	5},
	{">",							OP_TYPE_GREATER,	4},
	{"<",							OP_TYPE_LESS,		4},
	{">=",							OP_TYPE_GEQUAL,		4},
	{"<=",							OP_TYPE_LEQUAL,		4},
	{"==",							OP_TYPE_EQUAL,		3},
	{"!=",							OP_TYPE_NOTEQUAL,	3},
	{"&&",							OP_TYPE_AND,		2},
	{"||",							OP_TYPE_OR,			1},
	{NULL,							(opType_t)0,		0}
};

static materialExpression_t		r_materialExpression;

static bool	R_ParseExpressionValue (script_t *script, material_t *material);
static bool	R_ParseExpressionOperator (script_t *script, material_t *material);


/*
 ==================
 R_GetExpressionConstant
 ==================
*/
static bool R_GetExpressionConstant (material_t *material, float value, int *expressionRegister){

	if (material->numRegisters == MAX_EXPRESSION_REGISTERS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_EXPRESSION_REGISTERS hit in material '%s'\n", material->name);
		return false;
	}

	material->expressionRegisters[material->numRegisters] = value;

	*expressionRegister = r_materialExpression.resultRegister = material->numRegisters++;

	return true;
}

/*
 ==================
 R_GetExpressionTemporary
 ==================
*/
static bool R_GetExpressionTemporary (material_t *material, int *expressionRegister){

	if (material->numRegisters == MAX_EXPRESSION_REGISTERS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_EXPRESSION_REGISTERS hit in material '%s'\n", material->name);
		return false;
	}

	material->expressionRegisters[material->numRegisters] = 0.0f;

	*expressionRegister = r_materialExpression.resultRegister = material->numRegisters++;

	return true;
}

/*
 ==================
 R_EmitExpressionOp
 ==================
*/
static bool R_EmitExpressionOp (material_t *material, opType_t opType, int a, int b, int c){

	expOp_t	*op;

	if (material->numOps == MAX_EXPRESSION_OPS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_EXPRESSION_OPS hit in material '%s'\n", material->name);
		return false;
	}

	op = &material->expressionOps[material->numOps++];

	op->opType = opType;
	op->a = a;
	op->b = b;
	op->c = c;

	return true;
}

/*
 ==================
 R_AddExpressionValue
 ==================
*/
static bool R_AddExpressionValue (material_t *material, int expressionRegister){

	expValue_t		*v;

	if (r_materialExpression.numValues == MAX_EXPRESSION_VALUES){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_EXPRESSION_VALUES hit for expression in material '%s'\n", material->name);
		return false;
	}

	v = &r_materialExpression.values[r_materialExpression.numValues++];

	v->expressionRegister = expressionRegister;
	v->brackets = r_materialExpression.brackets;
	v->parentheses = r_materialExpression.parentheses[r_materialExpression.brackets];
	v->next = NULL;
	v->prev = r_materialExpression.lastValue;

	if (r_materialExpression.lastValue)
		r_materialExpression.lastValue->next = v;
	else
		r_materialExpression.firstValue = v;

	r_materialExpression.lastValue = v;

	return true;
}

/*
 ==================
 R_AddExpressionOperator
 ==================
*/
static bool R_AddExpressionOperator (material_t *material, opType_t opType, int priority){

	expOperator_t	*o;

	if (r_materialExpression.numOperators == MAX_EXPRESSION_OPERATORS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_EXPRESSION_OPERATORS hit for expression in material '%s'\n", material->name);
		return false;
	}

	o = &r_materialExpression.operators[r_materialExpression.numOperators++];

	o->opType = opType;
	o->priority = priority;
	o->brackets = r_materialExpression.brackets;
	o->parentheses = r_materialExpression.parentheses[r_materialExpression.brackets];
	o->next = NULL;
	o->prev = r_materialExpression.lastOperator;

	if (r_materialExpression.lastOperator)
		r_materialExpression.lastOperator->next = o;
	else
		r_materialExpression.firstOperator = o;

	r_materialExpression.lastOperator = o;

	return true;
}

/*
 ==================
 R_ParseExpressionValue
 ==================
*/
static bool R_ParseExpressionValue (script_t *script, material_t *material){

	token_t			token;
	table_t			*table;
	expValueTable_t	*expValue;
	int				expressionRegister;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: unexpected end of expression in material '%s'\n", material->name);
		return false;
	}

	switch (token.type){
	case TT_NUMBER:
		// It's a constant
		if (!R_GetExpressionConstant(material, token.floatValue, &expressionRegister))
			return false;

		if (!R_AddExpressionValue(material, expressionRegister))
			return false;

		break;
	case TT_NAME:
		// Check for a table
		table = R_FindTable(token.string);
		if (table){
			// The next token should be an opening bracket
			if (!PS_ExpectTokenString(script, &token, "[")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected '[', found '%s' instead for expression in material '%s'\n", token.string, material->name);
				return false;
			}

			r_materialExpression.brackets++;

			if (!R_AddExpressionValue(material, table->index))
				return false;

			if (!R_AddExpressionOperator(material, OP_TYPE_TABLE, 0))
				return false;

			// We still expect a value
			return R_ParseExpressionValue(script, material);
		}

		// Check for a variable
		for (expValue = r_expValueTable; expValue->name; expValue++){
			if (!Str_ICompare(expValue->name, token.string)){
				r_materialExpression.resultRegister = expValue->expressionRegister;

				if (!R_AddExpressionValue(material, expValue->expressionRegister))
					return false;

				break;
			}
		}

		if (!expValue->name){
			Com_Printf(S_COLOR_YELLOW "WARNING: invalid value '%s' for expression in material '%s'\n", token.string, material->name);
			return false;
		}

		break;
	case TT_PUNCTUATION:
		// Check for an opening parenthesis
		if (!Str_Compare(token.string, "(")){
			r_materialExpression.parentheses[r_materialExpression.brackets]++;

			// We still expect a value
			return R_ParseExpressionValue(script, material);
		}

		// Check for a minus operator before a constant
		if (!Str_Compare(token.string, "-")){
			if (!PS_ExpectTokenType(script, &token, TT_NUMBER, 0)){
				Com_Printf(S_COLOR_YELLOW "WARNING: invalid value '%s' for expression in material '%s'\n", token.string, material->name);
				return false;
			}

			if (!R_GetExpressionConstant(material, -token.floatValue, &expressionRegister))
				return false;

			if (!R_AddExpressionValue(material, expressionRegister))
				return false;
		}
		else {
			Com_Printf(S_COLOR_YELLOW "WARNING: invalid value '%s' for expression in material '%s'\n", token.string, material->name);
			return false;
		}

		break;
	default:
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value '%s' for expression in material '%s'\n", token.string, material->name);
		return false;
	}

	// We now expect an operator
	return R_ParseExpressionOperator(script, material);
}

/*
 ==================
 R_ParseExpressionOperator
 ==================
*/
static bool R_ParseExpressionOperator (script_t *script, material_t *material){

	token_t				token;
	expOperatorTable_t	*expOperator;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: unexpected end of expression in material '%s'\n", material->name);
		return false;
	}

	// A non-punctuation may be a material command, a comma separates command
	// arguments and a brace separates material definitions and stages
	if (token.type != TT_PUNCTUATION || !Str_Compare(token.string, ",") || !Str_Compare(token.string, "{") || !Str_Compare(token.string, "}")){
		if (r_materialExpression.brackets){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching ']' for expression in material '%s'\n", material->name);
			return false;
		}

		if (r_materialExpression.parentheses[r_materialExpression.brackets]){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching ')' for expression in material '%s'\n", material->name);
			return false;
		}

		PS_UnreadToken(script, &token);
		return true;
	}

	// Check for a closing bracket
	if (!Str_Compare(token.string, "]")){
		if (r_materialExpression.parentheses[r_materialExpression.brackets]){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching ')' for expression in material '%s'\n", material->name);
			return false;
		}

		r_materialExpression.brackets--;
		if (r_materialExpression.brackets < 0){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching '[' for expression in material '%s'\n", material->name);
			return false;
		}

		// We still expect an operator
		return R_ParseExpressionOperator(script, material);
	}

	// Check for a closing parenthesis
	if (!Str_Compare(token.string, ")")){
		r_materialExpression.parentheses[r_materialExpression.brackets]--;
		if (r_materialExpression.parentheses[r_materialExpression.brackets] < 0){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching '(' for expression in material '%s'\n", material->name);
			return false;
		}

		// We still expect an operator
		return R_ParseExpressionOperator(script, material);
	}

	// Check for an operator
	for (expOperator = r_expOperatorTable; expOperator->name; expOperator++){
		if (!Str_Compare(expOperator->name, token.string)){
			if (!R_AddExpressionOperator(material, expOperator->opType, expOperator->priority))
				return false;

			break;
		}
	}

	if (!expOperator->name){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid operator '%s' for expression in material '%s'\n", token.string, material->name);
		return false;
	}

	// We now expect a value
	return R_ParseExpressionValue(script, material);
}

/*
 ==================
 R_ParseExpression
 ==================
*/
static bool R_ParseExpression (script_t *script, material_t *material, int *expressionRegister){

	expValue_t		*v;
	expOperator_t	*o;
	int				a, b, c;

	// Clear the expression data
	memset(&r_materialExpression, 0, sizeof(materialExpression_t));

	// Parse the expression, starting with a value
	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS);

	if (!R_ParseExpressionValue(script, material)){
		PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);
		return false;
	}

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

	// Emit the expression ops, if any
	while (r_materialExpression.firstOperator){
		v = r_materialExpression.firstValue;

		for (o = r_materialExpression.firstOperator; o->next; o = o->next){
			// If the current operator is nested deeper in brackets than the
			// next operator
			if (o->brackets > o->next->brackets)
				break;

			// If the current and next operators are nested equally deep in
			// brackets
			if (o->brackets == o->next->brackets){
				// If the current operator is nested deeper in parentheses than
				// the next operator
				if (o->parentheses > o->next->parentheses)
					break;

				// If the current and next operators are nested equally deep in
				// parentheses
				if (o->parentheses == o->next->parentheses){
					// If the priority of the current operator is equal or
					// higher than the priority of the next operator
					if (o->priority >= o->next->priority)
						break;
				}
			}

			v = v->next;
		}

		// Get the source registers
		a = v->expressionRegister;
		b = v->next->expressionRegister;

		// Get the temporary register
		if (!R_GetExpressionTemporary(material, &c))
			return false;

		// Emit the expression op
		if (!R_EmitExpressionOp(material, o->opType, a, b, c))
			return false;

		// The temporary register for the current operation will be used as a
		// source for the next operation
		v->expressionRegister = c;

		// Remove the second value
		v = v->next;

		if (v->prev)
			v->prev->next = v->next;
		else
			r_materialExpression.firstValue = v->next;

		if (v->next)
			v->next->prev = v->prev;
		else
			r_materialExpression.lastValue = v->prev;

		// Remove the operator
		if (o->prev)
			o->prev->next = o->next;
		else
			r_materialExpression.firstOperator = o->next;

		if (o->next)
			o->next->prev = o->prev;
		else
			r_materialExpression.lastOperator = o->prev;
	}

	// The last temporary register will contain the result after evaluation
	*expressionRegister = r_materialExpression.resultRegister;

	return true;
}


/*
 ==============================================================================

 MATERIAL IMAGE PROGRAM PARSING

 ==============================================================================
*/


/*
 ==================
 R_ParseImageProgram
 ==================
*/
static bool R_ParseImageProgram (script_t *script, material_t *material, char *imageProgram, int imageProgramSize){

	token_t	token;
	int		depth = 1;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: unexpected end of image program in material '%s'\n", material->name);
		return false;
	}

	Str_Copy(imageProgram, token.string, imageProgramSize);

	// Check for an opening parenthesis
	if (!PS_CheckTokenString(script, &token, "("))
		return true;

	Str_Append(imageProgram, "(", imageProgramSize);

	// Read until a matching closing parenthesis is found
	do {
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: no matching ')' for image program in material '%s'\n", material->name);
			return false;
		}

		Str_Append(imageProgram, token.string, imageProgramSize);

		if (token.type == TT_PUNCTUATION){
			if (!Str_Compare(token.string, ","))
				Str_Append(imageProgram, " ", imageProgramSize);
			else if (!Str_Compare(token.string, "("))
				depth++;
			else if (!Str_Compare(token.string, ")"))
				depth--;
		}
	} while (depth);

	return true;
}


/*
 ==============================================================================

 MATERIAL PARSING

 ==============================================================================
*/


/*
 ==================
 R_ParseGlobalIf
 ==================
*/
static bool R_ParseGlobalIf (script_t *script, material_t *material){

	if (!R_ParseExpression(script, material, &material->conditionRegister)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'if' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseGlobalNoPicMip
 ==================
*/
static bool R_ParseGlobalNoPicMip (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureFlags[i] |= TF_NOPICMIP;

	return true;
}

/*
 ==================
 R_ParseGlobalUncompressed
 ==================
*/
static bool R_ParseGlobalUncompressed (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureFlags[i] |= TF_UNCOMPRESSED;

	return true;
}

/*
 ==================
 R_ParseGlobalHighQuality
 ==================
*/
static bool R_ParseGlobalHighQuality (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureFlags[i] |= (TF_NOPICMIP | TF_UNCOMPRESSED);

	return true;
}

/*
 ==================
 R_ParseGlobalNearest
 ==================
*/
static bool R_ParseGlobalNearest (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureFilter[i] = TF_NEAREST;

	return true;
}

/*
 ==================
 R_ParseGlobalLinear
 ==================
*/
static bool R_ParseGlobalLinear (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureFilter[i] = TF_LINEAR;

	return true;
}

/*
 ==================
 R_ParseGlobalClamp
 ==================
*/
static bool R_ParseGlobalClamp (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureWrap[i] = TW_CLAMP;

	return true;
}

/*
 ==================
 R_ParseGlobalZeroClamp
 ==================
*/
static bool R_ParseGlobalZeroClamp (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureWrap[i] = TW_CLAMP_TO_ZERO;

	return true;
}

/*
 ==================
 R_ParseGlobalAlphaZeroClamp
 ==================
*/
static bool R_ParseGlobalAlphaZeroClamp (script_t *script, material_t *material){

	int		i;

	for (i = 0; i < MAX_STAGES; i++)
		r_parseTextureWrap[i] = TW_CLAMP_TO_ZERO_ALPHA;

	return true;
}

/*
 ==================
 R_ParseGlobalNoOverlays
 ==================
*/
static bool R_ParseGlobalNoOverlays (script_t *script, material_t *material){

	material->flags |= MF_NOOVERLAYS;

	return true;
}

/*
 ==================
 R_ParseGlobalNoFog
 ==================
*/
static bool R_ParseGlobalNoFog (script_t *script, material_t *material){

	material->flags |= MF_NOFOG;

	return true;
}

/*
 ==================
 R_ParseGlobalNoShadows
 ==================
*/
static bool R_ParseGlobalNoShadows (script_t *script, material_t *material){

	material->flags |= MF_NOSHADOWS;

	return true;
}

/*
 ==================
 R_ParseGlobalNoSelfShadow
 ==================
*/
static bool R_ParseGlobalNoSelfShadow (script_t *script, material_t *material){

	material->flags |= MF_NOSELFSHADOW;

	return true;
}

/*
 ==================
 R_ParseGlobalAmbientLight
 ==================
*/
static bool R_ParseGlobalAmbientLight (script_t *script, material_t *material){

	if (material->type != MT_LIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'ambientLight' not allowed in material '%s'\n", material->name);
		return false;
	}

	material->lightType = LT_AMBIENT;

	return true;
}

/*
 ==================
 R_ParseGlobalFogLight
 ==================
*/
static bool R_ParseGlobalFogLight (script_t *script, material_t *material){

	if (material->type != MT_LIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'fogLight' not allowed in material '%s'\n", material->name);
		return false;
	}

	material->lightType = LT_FOG;

	return true;
}

/*
 ==================
 R_ParseGlobalBlendLight
 ==================
*/
static bool R_ParseGlobalBlendLight (script_t *script, material_t *material){

	if (material->type != MT_LIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'blendLight' not allowed in material '%s'\n", material->name);
		return false;
	}

	material->lightType = LT_BLEND;

	return true;
}

/*
 ==================
 R_ParseGlobalLightFalloffImage
 ==================
*/
static bool R_ParseGlobalLightFalloffImage (script_t *script, material_t *material){

	token_t	token;
	char	name[MAX_OSPATH];
	int		flags = TF_NOPICMIP | TF_LIGHTMAP;

	if (material->type != MT_LIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'lightFalloffImage' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (PS_CheckTokenString(script, &token, "uncompressed"))
		flags |= TF_UNCOMPRESSED;

	if (!R_ParseImageProgram(script, material, name, sizeof(name))){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'lightFalloffImage' in material '%s'\n", material->name);
		return false;
	}

	material->lightFalloffImage = R_FindTexture(name, flags, TF_LINEAR, TW_CLAMP);
	if (!material->lightFalloffImage){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseGlobalSpectrum
 ==================
*/
static bool R_ParseGlobalSpectrum (script_t *script, material_t *material){

	if (material->type != MT_GENERIC && material->type != MT_LIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'spectrum' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->spectrum)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'spectrum' in material '%s'\n", material->name);
		return false;
	}

	if (material->spectrum < 1){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %i for 'spectrum' in material '%s'\n", material->spectrum, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseGlobalSpecularExponent
 ==================
*/
static bool R_ParseGlobalSpecularExponent (script_t *script, material_t *material){

	float	specularExponent;
	int		i;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'specularExponent' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &specularExponent)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'specularExponent' in material '%s'\n", material->name);
		return false;
	}

	if (specularExponent <= 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'specularExponent' in material '%s'\n", specularExponent, material->name);
		return false;
	}

	for (i = 0; i < MAX_STAGES; i++)
		material->stages[i].specularExponent = specularExponent;

	return true;
}

/*
 ==================
 R_ParseGlobalSpecularBrightness
 ==================
*/
static bool R_ParseGlobalSpecularBrightness (script_t *script, material_t *material){

	float	specularBrightness;
	int		i;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'specularBrightness' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &specularBrightness)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'specularBrightness' in material '%s'\n", material->name);
		return false;
	}

	if (specularBrightness <= 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'specularBrightness' in material '%s'\n", specularBrightness, material->name);
		return false;
	}

	for (i = 0; i < MAX_STAGES; i++)
		material->stages[i].specularBrightness = specularBrightness;

	return true;
}

/*
 ==================
 R_ParseGlobalDecalInfo
 ==================
*/
static bool R_ParseGlobalDecalInfo (script_t *script, material_t *material){

	token_t	token;
	int		i;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'decalInfo' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &material->decalInfo.stayTime)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'decalInfo' in material '%s'\n", material->name);
		return false;
	}

	if (material->decalInfo.stayTime < 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'decalInfo' in material '%s'\n", material->decalInfo.stayTime, material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &material->decalInfo.fadeTime)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'decalInfo' in material '%s'\n", material->name);
		return false;
	}

	if (material->decalInfo.fadeTime < 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'decalInfo' in material '%s'\n", material->decalInfo.fadeTime, material->name);
		return false;
	}

	if (!PS_ExpectTokenString(script, &token, "(")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '(', found '%s' instead for 'decalInfo' in material '%s'\n", token.string, material->name);
		return false;
	}

	for (i = 0; i < 4; i++){
		if (!PS_ReadFloat(script, &material->decalInfo.startRGBA[i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'decalInfo' in material '%s'\n", material->name);
			return false;
		}
	}

	if (!PS_ExpectTokenString(script, &token, ")")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected ')', found '%s' instead for 'decalInfo' in material '%s'\n", token.string, material->name);
		return false;
	}

	if (!PS_ExpectTokenString(script, &token, "(")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '(', found '%s' instead for 'decalInfo' in material '%s'\n", token.string, material->name);
		return false;
	}

	for (i = 0; i < 4; i++){
		if (!PS_ReadFloat(script, &material->decalInfo.endRGBA[i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'decalInfo' in material '%s'\n", material->name);
			return false;
		}
	}

	if (!PS_ExpectTokenString(script, &token, ")")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected ')', found '%s' instead for 'decalInfo' in material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseGlobalSort
 ==================
*/
static bool R_ParseGlobalSort (script_t *script, material_t *material){

	token_t	token;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'sort' in material '%s'\n", material->name);
		return false;
	}

	if (!Str_ICompare(token.string, "opaque"))
		material->sort = SORT_OPAQUE;
	else if (!Str_ICompare(token.string, "sky"))
		material->sort = SORT_SKY;
	else if (!Str_ICompare(token.string, "decal"))
		material->sort = SORT_DECAL;
	else if (!Str_ICompare(token.string, "seeThrough"))
		material->sort = SORT_SEE_THROUGH;
	else if (!Str_ICompare(token.string, "banner"))
		material->sort = SORT_BANNER;
	else if (!Str_ICompare(token.string, "underwater"))
		material->sort = SORT_UNDERWATER;
	else if (!Str_ICompare(token.string, "water"))
		material->sort = SORT_WATER;
	else if (!Str_ICompare(token.string, "farthest"))
		material->sort = SORT_FARTHEST;
	else if (!Str_ICompare(token.string, "far"))
		material->sort = SORT_FAR;
	else if (!Str_ICompare(token.string, "medium"))
		material->sort = SORT_MEDIUM;
	else if (!Str_ICompare(token.string, "close"))
		material->sort = SORT_CLOSE;
	else if (!Str_ICompare(token.string, "additive"))
		material->sort = SORT_ADDITIVE;
	else if (!Str_ICompare(token.string, "almostNearest"))
		material->sort = SORT_ALMOST_NEAREST;
	else if (!Str_ICompare(token.string, "nearest"))
		material->sort = SORT_NEAREST;
	else if (!Str_ICompare(token.string, "postProcess"))
		material->sort = SORT_POST_PROCESS;
	else {
		material->sort = (sort_t)token.signedIntValue;

		if (material->sort <= SORT_BAD || material->sort > SORT_POST_PROCESS){
			Com_Printf(S_COLOR_YELLOW "WARNING: unknown 'sort' parameter '%s' in material '%s'\n", token.string, material->name);
			return false;
		}
	}

	return true;
}

/*
 ==================
 R_ParseGlobalBackSided
 ==================
*/
static bool R_ParseGlobalBackSided (script_t *script, material_t *material){

	material->cullType = CT_BACK_SIDED;

	return true;
}

/*
 ==================
 R_ParseGlobalTwoSided
 ==================
*/
static bool R_ParseGlobalTwoSided (script_t *script, material_t *material){

	material->cullType = CT_TWO_SIDED;

	return true;
}

/*
 ==================
 R_ParseGlobalPolygonOffset
 ==================
*/
static bool R_ParseGlobalPolygonOffset (script_t *script, material_t *material){

	material->flags |= MF_POLYGONOFFSET;

	return true;
}

/*
 ==================
 R_ParseGlobalDeform
 ==================
*/
static bool R_ParseGlobalDeform (script_t *script, material_t *material){

	token_t	token;
	int		i;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'deform' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'deform' in material '%s'\n", material->name);
		return false;
	}

	if (!Str_ICompare(token.string, "expand")){
		if (!R_ParseExpression(script, material, &material->deformRegisters[0])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'deform expand' in material '%s'\n", material->name);
			return false;
		}

		material->deform = DFRM_EXPAND;
	}
	else if (!Str_ICompare(token.string, "move")){
		for (i = 0; i < 3; i++){
			if (!R_ParseExpression(script, material, &material->deformRegisters[i])){
				Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'deform move' in material '%s'\n", material->name);
				return false;
			}

			if (i < 2){
				if (!PS_ExpectTokenString(script, &token, ",")){
					Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'deform move' in material '%s'\n", token.string, material->name);
					return false;
				}
			}
		}

		material->deform = DFRM_MOVE;
	}
	else if (!Str_ICompare(token.string, "sprite"))
		material->deform = DFRM_SPRITE;
	else if (!Str_ICompare(token.string, "tube"))
		material->deform = DFRM_TUBE;
	else if (!Str_ICompare(token.string, "flare")){
		if (!R_ParseExpression(script, material, &material->deformRegisters[0])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'deform flare' in material '%s'\n", material->name);
			return false;
		}

		material->deform = DFRM_FLARE;
	}
	else {
		Com_Printf(S_COLOR_YELLOW "WARNING: unknown 'deform' parameter '%s' in material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseGlobalBumpMap
 ==================
*/
static bool R_ParseGlobalBumpMap (script_t *script, material_t *material){

	stage_t	*stage;
	char	name[MAX_OSPATH];
	int		index = material->numStages;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'bumpMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->numStages == MAX_STAGES){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_STAGES hit in material '%s'\n", material->name);
		return false;
	}
	stage = &material->stages[material->numStages++];

	if (!R_ParseImageProgram(script, material, name, sizeof(name))){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'bumpMap' in material '%s'\n", material->name);
		return false;
	}

	stage->textureStage.texture = R_FindTexture(name, r_parseTextureFlags[index] | TF_NORMALMAP, r_parseTextureFilter[index], r_parseTextureWrap[index]);
	if (!stage->textureStage.texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
		return false;
	}

	stage->lighting = SL_BUMP;

	return true;
}

/*
 ==================
 R_ParseGlobalDiffuseMap
 ==================
*/
static bool R_ParseGlobalDiffuseMap (script_t *script, material_t *material){

	stage_t	*stage;
	char	name[MAX_OSPATH];
	int		index = material->numStages;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'diffuseMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->numStages == MAX_STAGES){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_STAGES hit in material '%s'\n", material->name);
		return false;
	}
	stage = &material->stages[material->numStages++];

	if (!R_ParseImageProgram(script, material, name, sizeof(name))){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'diffuseMap' in material '%s'\n", material->name);
		return false;
	}

	stage->textureStage.texture = R_FindTexture(name, r_parseTextureFlags[index], r_parseTextureFilter[index], r_parseTextureWrap[index]);
	if (!stage->textureStage.texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
		return false;
	}

	stage->lighting = SL_DIFFUSE;

	return true;
}

/*
 ==================
 R_ParseGlobalSpecularMap
 ==================
*/
static bool R_ParseGlobalSpecularMap (script_t *script, material_t *material){

	stage_t	*stage;
	char	name[MAX_OSPATH];
	int		index = material->numStages;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'specularMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->numStages == MAX_STAGES){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_STAGES hit in material '%s'\n", material->name);
		return false;
	}
	stage = &material->stages[material->numStages++];

	if (!R_ParseImageProgram(script, material, name, sizeof(name))){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'specularMap' in material '%s'\n", material->name);
		return false;
	}

	stage->textureStage.texture = R_FindTexture(name, r_parseTextureFlags[index], r_parseTextureFilter[index], r_parseTextureWrap[index]);
	if (!stage->textureStage.texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
		return false;
	}

	stage->lighting = SL_SPECULAR;

	return true;
}

/*
 ==================
 R_ParseStageIf
 ==================
*/
static bool R_ParseStageIf (script_t *script, material_t *material, stage_t *stage){

	if (!R_ParseExpression(script, material, &stage->conditionRegister)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'if' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageNoPicMip
 ==================
*/
static bool R_ParseStageNoPicMip (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureFlags[index] |= TF_NOPICMIP;

	return true;
}

/*
 ==================
 R_ParseStageUncompressed
 ==================
*/
static bool R_ParseStageUncompressed (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureFlags[index] |= TF_UNCOMPRESSED;

	return true;
}

/*
 ==================
 R_ParseStageHighQuality
 ==================
*/
static bool R_ParseStageHighQuality (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureFlags[index] |= (TF_NOPICMIP | TF_UNCOMPRESSED);

	return true;
}

/*
 ==================
 R_ParseStageNearest
 ==================
*/
static bool R_ParseStageNearest (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureFilter[index] = TF_NEAREST;

	return true;
}

/*
 ==================
 R_ParseStageLinear
 ==================
*/
static bool R_ParseStageLinear (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureFilter[index] = TF_LINEAR;

	return true;
}

/*
 ==================
 R_ParseStageNoClamp
 ==================
*/
static bool R_ParseStageNoClamp (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureWrap[index] = TW_REPEAT;

	return true;
}

/*
 ==================
 R_ParseStageClamp
 ==================
*/
static bool R_ParseStageClamp (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureWrap[index] = TW_CLAMP;

	return true;
}

/*
 ==================
 R_ParseStageZeroClamp
 ==================
*/
static bool R_ParseStageZeroClamp (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureWrap[index] = TW_CLAMP_TO_ZERO;

	return true;
}

/*
 ==================
 R_ParseStageAlphaZeroClamp
 ==================
*/
static bool R_ParseStageAlphaZeroClamp (script_t *script, material_t *material, stage_t *stage){

	int		index = material->numStages - 1;

	r_parseTextureWrap[index] = TW_CLAMP_TO_ZERO_ALPHA;

	return true;
}

/*
 ==================
 R_ParseStageMap
 ==================
*/
static bool R_ParseStageMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	char			name[MAX_OSPATH];
	int				index = material->numStages - 1;

	if (material->type == MT_LIGHT)
		r_parseTextureFlags[index] |= (TF_NOPICMIP | TF_LIGHTMAP);

	if (material->type == MT_NOMIP){
		r_parseTextureFlags[index] |= TF_NOPICMIP;

		if (r_parseTextureFilter[index] == TF_DEFAULT)
			r_parseTextureFilter[index] = TF_LINEAR;
	}

	if (stage->lighting == SL_BUMP)
		r_parseTextureFlags[index] |= TF_NORMALMAP;

	if (!R_ParseImageProgram(script, material, name, sizeof(name))){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'map' in material '%s'\n", material->name);
		return false;
	}

	textureStage->texture = R_FindTexture(name, r_parseTextureFlags[index], r_parseTextureFilter[index], r_parseTextureWrap[index]);
	if (!textureStage->texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageCubeMap
 ==================
*/
static bool R_ParseStageCubeMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				index = material->numStages - 1;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'cubeMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'cubeMap' in material '%s'\n", material->name);
		return false;
	}

	textureStage->texture = R_FindCubeMapTexture(token.string, r_parseTextureFlags[index], r_parseTextureFilter[index], TW_CLAMP, false);
	if (!textureStage->texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageCameraCubeMap
 ==================
*/
static bool R_ParseStageCameraCubeMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				index = material->numStages - 1;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'cameraCubeMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'cameraCubeMap' in material '%s'\n", material->name);
		return false;
	}

	textureStage->texture = R_FindCubeMapTexture(token.string, r_parseTextureFlags[index], r_parseTextureFilter[index], TW_CLAMP, true);
	if (!textureStage->texture){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageVideoMap
 ==================
*/
static bool R_ParseStageVideoMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;

	if (material->type != MT_GENERIC && material->type != MT_NOMIP){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'videoMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'videoMap' in material '%s'\n", material->name);
		return false;
	}

	textureStage->video = R_PlayVideo(token.string);
	if (!textureStage->video){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find video '%s' for material '%s'\n", token.string, material->name);
		return false;
	}

	textureStage->texture = tr.cinematicTexture;

	return true;
}

/*
 ==================
 R_ParseStagePortalSkyRenderMap
 ==================
*/
static bool R_ParseStagePortalSkyRenderMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'portalSkyRenderMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewType != ST_NONE && material->subviewType != ST_PORTALSKY){
		Com_Printf(S_COLOR_YELLOW "WARNING: multiple subview types for material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewWidth)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'portalSkyRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewWidth < 1 || material->subviewWidth > SCREEN_WIDTH){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid width value of %i for 'portalSkyRenderMap' in material '%s'\n", material->subviewWidth, material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewHeight)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'portalSkyRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewHeight < 1 || material->subviewHeight > SCREEN_HEIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid height value of %i for 'portalSkyRenderMap' in material '%s'\n", material->subviewHeight, material->name);
		return false;
	}

	textureStage->texture = tr.portalSkyTexture;

	material->subviewType = ST_PORTALSKY;

	return true;
}

/*
 ==================
 R_ParseStageMirrorRenderMap
 ==================
*/
static bool R_ParseStageMirrorRenderMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'mirrorRenderMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewType != ST_NONE && material->subviewType != ST_MIRROR){
		Com_Printf(S_COLOR_YELLOW "WARNING: multiple subview types for material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewWidth)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'mirrorRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewWidth < 1 || material->subviewWidth > SCREEN_WIDTH){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid width value of %i for 'mirrorRenderMap' in material '%s'\n", material->subviewWidth, material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewHeight)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'mirrorRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewHeight < 1 || material->subviewHeight > SCREEN_HEIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid height value of %i for 'mirrorRenderMap' in material '%s'\n", material->subviewHeight, material->name);
		return false;
	}

	textureStage->texture = tr.subviewTexture;

	material->subviewType = ST_MIRROR;

	return true;
}

/*
 ==================
 R_ParseStagePortalRenderMap
 ==================
*/
static bool R_ParseStagePortalRenderMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'portalRenderMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewType != ST_NONE && material->subviewType != ST_PORTAL){
		Com_Printf(S_COLOR_YELLOW "WARNING: multiple subview types for material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewWidth)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'portalRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewWidth < 1 || material->subviewWidth > SCREEN_WIDTH){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid width value of %i for 'portalRenderMap' in material '%s'\n", material->subviewWidth, material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewHeight)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'portalRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewHeight < 1 || material->subviewHeight > SCREEN_HEIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid height value of %i for 'portalRenderMap' in material '%s'\n", material->subviewHeight, material->name);
		return false;
	}

	textureStage->texture = tr.subviewTexture;

	material->subviewType = ST_PORTAL;

	return true;
}

/*
 ==================
 R_ParseStageRemoteRenderMap
 ==================
*/
static bool R_ParseStageRemoteRenderMap (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'remoteRenderMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewType != ST_NONE && material->subviewType != ST_REMOTE){
		Com_Printf(S_COLOR_YELLOW "WARNING: multiple subview types for material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewWidth)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'remoteRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewWidth < 1 || material->subviewWidth > SCREEN_WIDTH){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid width value of %i for 'remoteRenderMap' in material '%s'\n", material->subviewWidth, material->name);
		return false;
	}

	if (!PS_ReadSignedInt(script, &material->subviewHeight)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'remoteRenderMap' in material '%s'\n", material->name);
		return false;
	}

	if (material->subviewHeight < 1 || material->subviewHeight > SCREEN_HEIGHT){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid height value of %i for 'remoteRenderMap' in material '%s'\n", material->subviewHeight, material->name);
		return false;
	}

	textureStage->texture = tr.subviewTexture;

	material->subviewType = ST_REMOTE;

	return true;
}

/*
 ==================
 R_ParseStageTexGen
 ==================
*/
static bool R_ParseStageTexGen (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'texGen' in material '%s'\n", material->name);
		return false;
	}

	if (!Str_ICompare(token.string, "explicit"))
		textureStage->texGen = TG_EXPLICIT;
	else if (!Str_ICompare(token.string, "reflect"))
		textureStage->texGen = TG_REFLECT;
	else if (!Str_ICompare(token.string, "normal"))
		textureStage->texGen = TG_NORMAL;
	else if (!Str_ICompare(token.string, "skyBox"))
		textureStage->texGen = TG_SKYBOX;
	else if (!Str_ICompare(token.string, "portalSky"))
		textureStage->texGen = TG_PORTALSKY;
	else if (!Str_ICompare(token.string, "screen"))
		textureStage->texGen = TG_SCREEN;
	else {
		Com_Printf(S_COLOR_YELLOW "WARNING: unknown 'texGen' parameter '%s' in material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageTranslate
 ==================
*/
static bool R_ParseStageTranslate (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				i;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < 2; i++){
		if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'translate' in material '%s'\n", material->name);
			return false;
		}

		if (i < 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'translate' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_TRANSLATE;

	return true;
}

/*
 ==================
 R_ParseStageScroll
 ==================
*/
static bool R_ParseStageScroll (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				i;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < 2; i++){
		if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'scroll' in material '%s'\n", material->name);
			return false;
		}

		if (i < 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'scroll' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_SCROLL;

	return true;
}

/*
 ==================
 R_ParseStageScale
 ==================
*/
static bool R_ParseStageScale (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				i;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < 2; i++){
		if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'scale' in material '%s'\n", material->name);
			return false;
		}

		if (i < 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'scale' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_SCALE;

	return true;
}

/*
 ==================
 R_ParseStageCenterScale
 ==================
*/
static bool R_ParseStageCenterScale (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				i;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < 2; i++){
		if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'centerScale' in material '%s'\n", material->name);
			return false;
		}

		if (i < 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'centerScale' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_CENTERSCALE;

	return true;
}

/*
 ==================
 R_ParseStageShear
 ==================
*/
static bool R_ParseStageShear (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;
	token_t			token;
	int				i;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < 2; i++){
		if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'shear' in material '%s'\n", material->name);
			return false;
		}

		if (i < 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'shear' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_SHEAR;

	return true;
}

/*
 ==================
 R_ParseStageRotate
 ==================
*/
static bool R_ParseStageRotate (script_t *script, material_t *material, stage_t *stage){

	textureStage_t	*textureStage = &stage->textureStage;

	if (textureStage->numTexMods == MAX_TEXMODS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TEXMODS hit in material '%s'\n", material->name);
		return false;
	}

	if (!R_ParseExpression(script, material, &textureStage->texModsRegisters[textureStage->numTexMods][0])){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'rotate' in material '%s'\n", material->name);
		return false;
	}

	textureStage->texMods[textureStage->numTexMods++] = TM_ROTATE;

	return true;
}

/*
 ==================
 R_ParseStageRed
 ==================
*/
static bool R_ParseStageRed (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	if (!R_ParseExpression(script, material, &colorStage->registers[0])){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'red' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageGreen
 ==================
*/
static bool R_ParseStageGreen (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	if (!R_ParseExpression(script, material, &colorStage->registers[1])){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'green' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageBlue
 ==================
*/
static bool R_ParseStageBlue (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	if (!R_ParseExpression(script, material, &colorStage->registers[2])){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'blue' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageAlpha
 ==================
*/
static bool R_ParseStageAlpha (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	if (!R_ParseExpression(script, material, &colorStage->registers[3])){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'alpha' in material '%s'\n", material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageRGB
 ==================
*/
static bool R_ParseStageRGB (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;
	int				expressionRegister;

	if (!R_ParseExpression(script, material, &expressionRegister)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'rgb' in material '%s'\n", material->name);
		return false;
	}

	colorStage->registers[0] = expressionRegister;
	colorStage->registers[1] = expressionRegister;
	colorStage->registers[2] = expressionRegister;

	return true;
}

/*
 ==================
 R_ParseStageRGBA
 ==================
*/
static bool R_ParseStageRGBA (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;
	int				expressionRegister;

	if (!R_ParseExpression(script, material, &expressionRegister)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'rgba' in material '%s'\n", material->name);
		return false;
	}

	colorStage->registers[0] = expressionRegister;
	colorStage->registers[1] = expressionRegister;
	colorStage->registers[2] = expressionRegister;
	colorStage->registers[3] = expressionRegister;

	return true;
}

/*
 ==================
 R_ParseStageColor
 ==================
*/
static bool R_ParseStageColor (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;
	token_t			token;
	int				i;

	for (i = 0; i < 4; i++){
		if (!R_ParseExpression(script, material, &colorStage->registers[i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'color' in material '%s'\n", material->name);
			return false;
		}

		if (i < 3){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'color' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	return true;
}

/*
 ==================
 R_ParseStageColored
 ==================
*/
static bool R_ParseStageColored (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	colorStage->registers[0] = EXP_REGISTER_PARM0;
	colorStage->registers[1] = EXP_REGISTER_PARM1;
	colorStage->registers[2] = EXP_REGISTER_PARM2;
	colorStage->registers[3] = EXP_REGISTER_PARM3;

	return true;
}

/*
 ==================
 R_ParseStageVertexColor
 ==================
*/
static bool R_ParseStageVertexColor (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	colorStage->vertexColor = VC_MODULATE;

	return true;
}

/*
 ==================
 R_ParseStageInverseVertexColor
 ==================
*/
static bool R_ParseStageInverseVertexColor (script_t *script, material_t *material, stage_t *stage){

	colorStage_t	*colorStage = &stage->colorStage;

	colorStage->vertexColor = VC_INVERSE_MODULATE;

	return true;
}

/*
 ==================
 R_ParseStageVertexShader
 ==================
*/
static bool R_ParseStageVertexShader (script_t *script, material_t *material, stage_t *stage){

	shaderStage_t	*shaderStage = &stage->shaderStage;
	token_t			token;

	if (material->type != MT_GENERIC && material->type != MT_NOMIP){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'vertexShader' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'vertexShader' in material '%s'\n", material->name);
		return false;
	}

	shaderStage->vertexShader = R_FindShader(token.string, SHADER_VERTEX);
	if (!shaderStage->vertexShader){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find vertex shader '%s' for material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageFragmentShader
 ==================
*/
static bool R_ParseStageFragmentShader (script_t *script, material_t *material, stage_t *stage){

	shaderStage_t	*shaderStage = &stage->shaderStage;
	token_t			token;

	if (material->type != MT_GENERIC && material->type != MT_NOMIP){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'fragmentShader' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'fragmentShader' in material '%s'\n", material->name);
		return false;
	}

	shaderStage->fragmentShader = R_FindShader(token.string, SHADER_FRAGMENT);
	if (!shaderStage->fragmentShader){
		Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find fragment shader '%s' for material '%s'\n", token.string, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageShaderParm
 ==================
*/
static bool R_ParseStageShaderParm (script_t *script, material_t *material, stage_t *stage){

	shaderStage_t	*shaderStage = &stage->shaderStage;
	shaderParm_t	*shaderParm;
	token_t			token;
	int				i, parms;

	if (material->type != MT_GENERIC && material->type != MT_NOMIP){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'shaderParm' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!shaderStage->program){
		if (!shaderStage->vertexShader || !shaderStage->fragmentShader){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'shaderParm' not allowed before 'vertexShader' and 'fragmentShader' in material '%s'\n", material->name);
			return false;
		}

		shaderStage->program = R_FindProgram(shaderStage->vertexShader, shaderStage->fragmentShader);
		if (!shaderStage->program){
			Com_Printf(S_COLOR_YELLOW "WARNING: invalid shaders in material '%s'\n", material->name);
			return false;
		}
	}

	if (shaderStage->numShaderParms == MAX_SHADER_PARMS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_SHADER_PARMS hit in material '%s'\n", material->name);
		return false;
	}
	shaderParm = &shaderStage->shaderParms[shaderStage->numShaderParms++];

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderParm' in material '%s'\n", material->name);
		return false;
	}

	shaderParm->uniform = R_GetProgramUniform(shaderStage->program, token.string);
	if (!shaderParm->uniform || shaderParm->uniform->type != UT_CUSTOM){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform name for 'shaderParm' in material '%s'\n", material->name);
		return false;
	}

	if (shaderParm->uniform->size != 1){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform size for 'shaderParm' in material '%s'\n", material->name);
		return false;
	}

	switch (shaderParm->uniform->format){
	case GL_FLOAT:
		parms = 1;
		break;
	case GL_FLOAT_VEC2:
		parms = 2;
		break;
	case GL_FLOAT_VEC3:
		parms = 3;
		break;
	case GL_FLOAT_VEC4:
		parms = 4;
		break;
	default:
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform format for 'shaderParm' in material '%s'\n", material->name);
		return false;
	}

	for (i = 0; i < parms; i++){
		if (!R_ParseExpression(script, material, &shaderParm->registers[i])){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'shaderParm' in material '%s'\n", material->name);
			return false;
		}

		if (i < parms - 1){
			if (!PS_ExpectTokenString(script, &token, ",")){
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'shaderParm' in material '%s'\n", token.string, material->name);
				return false;
			}
		}
	}

	return true;
}

/*
 ==================
 R_ParseStageShaderMap
 ==================
*/
static bool R_ParseStageShaderMap (script_t *script, material_t *material, stage_t *stage){

	shaderStage_t	*shaderStage = &stage->shaderStage;
	shaderMap_t		*shaderMap;
	uniform_t		*uniform;
	char			name[MAX_OSPATH];
	token_t			token;
	int				index = material->numStages - 1;

	if (material->type != MT_GENERIC && material->type != MT_NOMIP){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'shaderMap' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!shaderStage->program){
		if (!shaderStage->vertexShader || !shaderStage->fragmentShader){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'shaderMap' not allowed before 'vertexShader' and 'fragmentShader' in material '%s'\n", material->name);
			return false;
		}

		shaderStage->program = R_FindProgram(shaderStage->vertexShader, shaderStage->fragmentShader);
		if (!shaderStage->program){
			Com_Printf(S_COLOR_YELLOW "WARNING: invalid shaders in material '%s'\n", material->name);
			return false;
		}
	}

	if (shaderStage->numShaderMaps == MAX_SHADER_MAPS){
		Com_Printf(S_COLOR_YELLOW "WARNING: MAX_SHADER_MAPS hit in material '%s'\n", material->name);
		return false;
	}
	shaderMap = &shaderStage->shaderMaps[shaderStage->numShaderMaps++];

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
		return false;
	}

	uniform = R_GetProgramUniform(shaderStage->program, token.string);
	if (!uniform || uniform->type != UT_CUSTOM){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform name for 'shaderMap' in material '%s'\n", material->name);
		return false;
	}

	if (uniform->size != 1){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform size for 'shaderMap' in material '%s'\n", material->name);
		return false;
	}

	switch (uniform->format){
	case GL_SAMPLER_2D:
	case GL_SAMPLER_CUBE:
		R_SetProgramSampler(shaderStage->program, uniform, shaderStage->numShaderMaps - 1);
		break;
	default:
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid uniform format for 'shaderMap' in material '%s'\n", material->name);
		return false;
	}

	while (1){
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		if (!Str_ICompare(token.string, "normalMap"))
			r_parseTextureFlags[index] |= TF_NORMALMAP;
		else if (!Str_ICompare(token.string, "noPicMip"))
			r_parseTextureFlags[index] |= TF_NOPICMIP;
		else if (!Str_ICompare(token.string, "uncompressed"))
			r_parseTextureFlags[index] |= TF_UNCOMPRESSED;
		else if (!Str_ICompare(token.string, "highQuality"))
			r_parseTextureFlags[index] |= (TF_NOPICMIP | TF_UNCOMPRESSED);
		else if (!Str_ICompare(token.string, "nearest"))
			r_parseTextureFilter[index] = TF_NEAREST;
		else if (!Str_ICompare(token.string, "linear"))
			r_parseTextureFilter[index] = TF_LINEAR;
		else if (!Str_ICompare(token.string, "noClamp"))
			r_parseTextureWrap[index] = TW_REPEAT;
		else if (!Str_ICompare(token.string, "clamp"))
			r_parseTextureWrap[index] = TW_CLAMP;
		else if (!Str_ICompare(token.string, "zeroClamp"))
			r_parseTextureWrap[index] = TW_CLAMP_TO_ZERO;
		else if (!Str_ICompare(token.string, "alphaZeroClamp"))
			r_parseTextureWrap[index] = TW_CLAMP_TO_ZERO_ALPHA;
		else
			break;
	}

	if (material->type == MT_NOMIP){
		r_parseTextureFlags[index] |= TF_NOPICMIP;

		if (r_parseTextureFilter[index] == TF_DEFAULT)
			r_parseTextureFilter[index] = TF_LINEAR;
	}

	if (!Str_ICompare(token.string, "cubeMap")){
		if (material->type != MT_GENERIC){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'cubeMap' not allowed for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		shaderMap->texture = R_FindCubeMapTexture(token.string, r_parseTextureFlags[index], r_parseTextureFilter[index], TW_CLAMP, false);
		if (!shaderMap->texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", token.string, material->name);
			return false;
		}
	}
	else if (!Str_ICompare(token.string, "cameraCubeMap")){
		if (material->type != MT_GENERIC){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'cameraCubeMap' not allowed for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		shaderMap->texture = R_FindCubeMapTexture(token.string, r_parseTextureFlags[index], r_parseTextureFilter[index], TW_CLAMP, true);
		if (!shaderMap->texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", token.string, material->name);
			return false;
		}
	}
	else if (!Str_ICompare(token.string, "videoMap")){
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		shaderMap->video = R_PlayVideo(token.string);
		if (!shaderMap->video){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find video '%s' for material '%s'\n", token.string, material->name);
			return false;
		}

		shaderMap->texture = tr.cinematicTexture;
	}
	else {
		PS_UnreadToken(script, &token);

		if (!R_ParseImageProgram(script, material, name, sizeof(name))){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'shaderMap' in material '%s'\n", material->name);
			return false;
		}

		shaderMap->texture = R_FindTexture(name, r_parseTextureFlags[index], r_parseTextureFilter[index], r_parseTextureWrap[index]);
		if (!shaderMap->texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture '%s' for material '%s'\n", name, material->name);
			return false;
		}
	}

	return true;
}

/*
 ==================
 R_ParseStageSpecularExponent
 ==================
*/
static bool R_ParseStageSpecularExponent (script_t *script, material_t *material, stage_t *stage){

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'specularExponent' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &stage->specularExponent)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'specularExponent' in material '%s'\n", material->name);
		return false;
	}

	if (stage->specularExponent <= 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'specularExponent' in material '%s'\n", stage->specularExponent, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStageSpecularBrightness
 ==================
*/
static bool R_ParseStageSpecularBrightness (script_t *script, material_t *material, stage_t *stage){

	if (material->type != MT_GENERIC){
		Com_Printf(S_COLOR_YELLOW "WARNING: 'specularBrightness' not allowed in material '%s'\n", material->name);
		return false;
	}

	if (!PS_ReadFloat(script, &stage->specularBrightness)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'specularBrightness' in material '%s'\n", material->name);
		return false;
	}

	if (stage->specularBrightness <= 0.0f){
		Com_Printf(S_COLOR_YELLOW "WARNING: invalid value of %g for 'specularBrightness' in material '%s'\n", stage->specularBrightness, material->name);
		return false;
	}

	return true;
}

/*
 ==================
 R_ParseStagePrivatePolygonOffset
 ==================
*/
static bool R_ParseStagePrivatePolygonOffset (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_POLYGONOFFSET;

	return true;
}

/*
 ==================
 R_ParseStageBlend
 ==================
*/
static bool R_ParseStageBlend (script_t *script, material_t *material, stage_t *stage){

	token_t	token;

	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'blend' in material '%s'\n", material->name);
		return false;
	}

	if (!Str_ICompare(token.string, "bumpMap")){
		if (material->type != MT_GENERIC){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'blend bumpMap' not allowed in material '%s'\n", material->name);
			return false;
		}

		stage->lighting = SL_BUMP;
		return true;
	}
	if (!Str_ICompare(token.string, "diffuseMap")){
		if (material->type != MT_GENERIC){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'blend diffuseMap' not allowed in material '%s'\n", material->name);
			return false;
		}

		stage->lighting = SL_DIFFUSE;
		return true;
	}
	if (!Str_ICompare(token.string, "specularMap")){
		if (material->type != MT_GENERIC){
			Com_Printf(S_COLOR_YELLOW "WARNING: 'blend specularMap' not allowed in material '%s'\n", material->name);
			return false;
		}

		stage->lighting = SL_SPECULAR;
		return true;
	}

	if (!Str_ICompare(token.string, "none")){
		stage->blendSrc = GL_ZERO;
		stage->blendDst = GL_ONE;
	}
	else if (!Str_ICompare(token.string, "add")){
		stage->blendSrc = GL_ONE;
		stage->blendDst = GL_ONE;
	}
	else if (!Str_ICompare(token.string, "blend")){
		stage->blendSrc = GL_SRC_ALPHA;
		stage->blendDst = GL_ONE_MINUS_SRC_ALPHA;
	}
	else if (!Str_ICompare(token.string, "filter") || !Str_ICompare(token.string, "modulate")){
		stage->blendSrc = GL_DST_COLOR;
		stage->blendDst = GL_ZERO;
	}
	else {
		if (!Str_ICompare(token.string, "GL_ZERO"))
			stage->blendSrc = GL_ZERO;
		else if (!Str_ICompare(token.string, "GL_ONE"))
			stage->blendSrc = GL_ONE;
		else if (!Str_ICompare(token.string, "GL_SRC_COLOR"))
			stage->blendDst = GL_SRC_COLOR;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_SRC_COLOR"))
			stage->blendDst = GL_ONE_MINUS_SRC_COLOR;
		else if (!Str_ICompare(token.string, "GL_DST_COLOR"))
			stage->blendSrc = GL_DST_COLOR;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_DST_COLOR"))
			stage->blendSrc = GL_ONE_MINUS_DST_COLOR;
		else if (!Str_ICompare(token.string, "GL_SRC_ALPHA"))
			stage->blendSrc = GL_SRC_ALPHA;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_SRC_ALPHA"))
			stage->blendSrc = GL_ONE_MINUS_SRC_ALPHA;
		else if (!Str_ICompare(token.string, "GL_DST_ALPHA"))
			stage->blendSrc = GL_DST_ALPHA;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_DST_ALPHA"))
			stage->blendSrc = GL_ONE_MINUS_DST_ALPHA;
		else if (!Str_ICompare(token.string, "GL_SRC_ALPHA_SATURATE"))
			stage->blendSrc = GL_SRC_ALPHA_SATURATE;
		else {
			Com_Printf(S_COLOR_YELLOW "WARNING: unknown 'blend' parameter '%s' in material '%s'\n", token.string, material->name);
			return false;
		}

		if (!PS_ExpectTokenString(script, &token, ",")){
			Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'blend' in material '%s'\n", token.string, material->name);
			return false;
		}

		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'blend' in material '%s'\n", material->name);
			return false;
		}

		if (!Str_ICompare(token.string, "GL_ZERO"))
			stage->blendDst = GL_ZERO;
		else if (!Str_ICompare(token.string, "GL_ONE"))
			stage->blendDst = GL_ONE;
		else if (!Str_ICompare(token.string, "GL_SRC_COLOR"))
			stage->blendDst = GL_SRC_COLOR;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_SRC_COLOR"))
			stage->blendDst = GL_ONE_MINUS_SRC_COLOR;
		else if (!Str_ICompare(token.string, "GL_DST_COLOR"))
			stage->blendSrc = GL_DST_COLOR;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_DST_COLOR"))
			stage->blendSrc = GL_ONE_MINUS_DST_COLOR;
		else if (!Str_ICompare(token.string, "GL_SRC_ALPHA"))
			stage->blendDst = GL_SRC_ALPHA;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_SRC_ALPHA"))
			stage->blendDst = GL_ONE_MINUS_SRC_ALPHA;
		else if (!Str_ICompare(token.string, "GL_DST_ALPHA"))
			stage->blendDst = GL_DST_ALPHA;
		else if (!Str_ICompare(token.string, "GL_ONE_MINUS_DST_ALPHA"))
			stage->blendDst = GL_ONE_MINUS_DST_ALPHA;
		else {
			Com_Printf(S_COLOR_YELLOW "WARNING: unknown 'blend' parameter '%s' in material '%s'\n", token.string, material->name);
			return false;
		}
	}

	stage->flags |= SF_BLEND;

	return true;
}

/*
 ==================
 R_ParseStageAlphaTest
 ==================
*/
static bool R_ParseStageAlphaTest (script_t *script, material_t *material, stage_t *stage){

	if (!R_ParseExpression(script, material, &stage->alphaTestRegister)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'alphaTest' in material '%s'\n", material->name);
		return false;
	}

	stage->flags |= SF_ALPHATEST;

	return true;
}

/*
 ==================
 R_ParseStageIgnoreAlphaTest
 ==================
*/
static bool R_ParseStageIgnoreAlphaTest (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_IGNOREALPHATEST;

	return true;
}

/*
 ==================
 R_ParseStageMaskRed
 ==================
*/
static bool R_ParseStageMaskRed (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_MASKRED;

	return true;
}

/*
 ==================
 R_ParseStageMaskGreen
 ==================
*/
static bool R_ParseStageMaskGreen (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_MASKGREEN;

	return true;
}

/*
 ==================
 R_ParseStageMaskBlue
 ==================
*/
static bool R_ParseStageMaskBlue (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_MASKBLUE;

	return true;
}

/*
 ==================
 R_ParseStageMaskColor
 ==================
*/
static bool R_ParseStageMaskColor (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= (SF_MASKRED | SF_MASKGREEN | SF_MASKBLUE);

	return true;
}

/*
 ==================
 R_ParseStageMaskAlpha
 ==================
*/
static bool R_ParseStageMaskAlpha (script_t *script, material_t *material, stage_t *stage){

	stage->flags |= SF_MASKALPHA;

	return true;
}

/*
 ==================
 R_ParseStageGlowStage
 ==================
*/
static bool R_ParseStageGlowStage (script_t *script, material_t *material, stage_t *stage){

	stage->isGlow = true;

	return true;
}


// ============================================================================

typedef struct {
	const char *				name;
	int							parms;
} materialUtilityCmd_t;

typedef struct {
	const char *				name;
	bool						(*parseCmd)(script_t *, material_t *);
} materialGlobalCmd_t;

typedef struct {
	const char *				name;
	bool						(*parseCmd)(script_t *, material_t *, stage_t *);
} materialStageCmd_t;

static materialUtilityCmd_t		r_materialUtilityCmds[] = {
	{"qer_editorImage",				1},
	{"qer_trans",					1},
	{"qer_noCarve",					0},
	{"origin",						0},
	{"foliage",						6},
	{"tessSize",					1},
	{"backMaterial",				1},
	{"grass",						0},
	{"moss",						0},
	{"rock",						0},
	{"gravel",						0},
	{"dirt",						0},
	{"mud",							0},
	{"sand",						0},
	{"snow",						0},
	{"ice",							0},
	{"liquid",						0},
	{"flesh",						0},
	{"armor",						0},
	{"metal",						0},
	{"concrete",					0},
	{"brick",						0},
	{"plaster",						0},
	{"clay",						0},
	{"marble",						0},
	{"ceramic",						0},
	{"asphalt",						0},
	{"wood",						0},
	{"glass",						0},
	{"bpGlass",						0},
	{"plastic",						0},
	{"rubber",						0},
	{"foam",						0},
	{"cardboard",					0},
	{"carpet",						0},
	{"cloth",						0},
	{"electronic",					0},
	{"forceField",					0},
	{"hint",						0},
	{"discrete",					0},
	{"portalSky",					0},
	{"slick",						0},
	{"bounce",						0},
	{"ladder",						0},
	{"noDamage",					0},
	{"noImpact",					0},
	{"noSteps",						0},
	{"noClip",						0},
	{"noDraw",						0},
	{"noTJunc",						0},
	{"nonSolid",					0},
	{"solid",						0},
	{"translucent",					0},
	{"water",						0},
	{"slime",						0},
	{"lava",						0},
	{"playerClip",					0},
	{"vehicleClip",					0},
	{"moveableClip",				0},
	{"missileClip",					0},
	{"fullClip",					0},
	{"detail",						0},
	{"structural",					0},
	{"areaPortal",					0},
	{"noDrop",						0},
	{NULL,							0}
};

static materialGlobalCmd_t		r_materialGlobalCmds[] = {
	{"if",							R_ParseGlobalIf},
	{"noPicMip",					R_ParseGlobalNoPicMip},
	{"uncompressed",				R_ParseGlobalUncompressed},
	{"highQuality",					R_ParseGlobalHighQuality},
	{"nearest",						R_ParseGlobalNearest},
	{"linear",						R_ParseGlobalLinear},
	{"clamp",						R_ParseGlobalClamp},
	{"zeroClamp",					R_ParseGlobalZeroClamp},
	{"alphaZeroClamp",				R_ParseGlobalAlphaZeroClamp},
	{"noOverlays",					R_ParseGlobalNoOverlays},
	{"noFog",						R_ParseGlobalNoFog},
	{"noShadows",					R_ParseGlobalNoShadows},
	{"noSelfShadow",				R_ParseGlobalNoSelfShadow},
	{"ambientLight",				R_ParseGlobalAmbientLight},
	{"fogLight",					R_ParseGlobalFogLight},
	{"blendLight",					R_ParseGlobalBlendLight},
	{"lightFalloffImage",			R_ParseGlobalLightFalloffImage},
	{"spectrum",					R_ParseGlobalSpectrum},
	{"specularExponent",			R_ParseGlobalSpecularExponent},
	{"specularBrightness",			R_ParseGlobalSpecularBrightness},
	{"decalInfo",					R_ParseGlobalDecalInfo},
	{"sort",						R_ParseGlobalSort},
	{"backSided",					R_ParseGlobalBackSided},
	{"twoSided",					R_ParseGlobalTwoSided},
	{"polygonOffset",				R_ParseGlobalPolygonOffset},
	{"deform",						R_ParseGlobalDeform},
	{"bumpMap",						R_ParseGlobalBumpMap},
	{"diffuseMap",					R_ParseGlobalDiffuseMap},
	{"specularMap",					R_ParseGlobalSpecularMap},
	{NULL,							NULL}
};

static materialStageCmd_t		r_materialStageCmds[] = {
	{"if",							R_ParseStageIf},
	{"noPicMip",					R_ParseStageNoPicMip},
	{"uncompressed",				R_ParseStageUncompressed},
	{"highQuality",					R_ParseStageHighQuality},
	{"nearest",						R_ParseStageNearest},
	{"linear",						R_ParseStageLinear},
	{"noClamp",						R_ParseStageNoClamp},
	{"clamp",						R_ParseStageClamp},
	{"zeroClamp",					R_ParseStageZeroClamp},
	{"alphaZeroClamp",				R_ParseStageAlphaZeroClamp},
	{"map",							R_ParseStageMap},
	{"cubeMap",						R_ParseStageCubeMap},
	{"cameraCubeMap",				R_ParseStageCameraCubeMap},
	{"videoMap",					R_ParseStageVideoMap},
	{"portalSkyRenderMap",			R_ParseStagePortalSkyRenderMap},
	{"mirrorRenderMap",				R_ParseStageMirrorRenderMap},
	{"portalRenderMap",				R_ParseStagePortalRenderMap},
	{"remoteRenderMap",				R_ParseStageRemoteRenderMap},
	{"texGen",						R_ParseStageTexGen},
	{"translate",					R_ParseStageTranslate},
	{"scroll",						R_ParseStageScroll},
	{"scale",						R_ParseStageScale},
	{"centerScale",					R_ParseStageCenterScale},
	{"shear",						R_ParseStageShear},
	{"rotate",						R_ParseStageRotate},
	{"red",							R_ParseStageRed},
	{"green",						R_ParseStageGreen},
	{"blue",						R_ParseStageBlue},
	{"alpha",						R_ParseStageAlpha},
	{"rgb",							R_ParseStageRGB},
	{"rgba",						R_ParseStageRGBA},
	{"color",						R_ParseStageColor},
	{"colored",						R_ParseStageColored},
	{"vertexColor",					R_ParseStageVertexColor},
	{"inverseVertexColor",			R_ParseStageInverseVertexColor},
	{"vertexShader",				R_ParseStageVertexShader},
	{"fragmentShader",				R_ParseStageFragmentShader},
	{"shaderParm",					R_ParseStageShaderParm},
	{"shaderMap",					R_ParseStageShaderMap},
	{"specularExponent",			R_ParseStageSpecularExponent},
	{"specularBrightness",			R_ParseStageSpecularBrightness},
	{"privatePolygonOffset",		R_ParseStagePrivatePolygonOffset},
	{"blend",						R_ParseStageBlend},
	{"alphaTest",					R_ParseStageAlphaTest},
	{"ignoreAlphaTest",				R_ParseStageIgnoreAlphaTest},
	{"maskRed",						R_ParseStageMaskRed},
	{"maskGreen",					R_ParseStageMaskGreen},
	{"maskBlue",					R_ParseStageMaskBlue},
	{"maskColor",					R_ParseStageMaskColor},
	{"maskAlpha",					R_ParseStageMaskAlpha},
	{"glowStage",					R_ParseStageGlowStage},
	{NULL,							NULL}
};


/*
 ==================
 R_IsUtilityCommand
 ==================
*/
static bool R_IsUtilityCommand (script_t *script, const char *command, material_t *material){

	materialUtilityCmd_t	*cmd;
	token_t					token;
	int						i;

	for (cmd = r_materialUtilityCmds; cmd->name; cmd++){
		if (!Str_ICompare(cmd->name, command)){
			if (!cmd->parms)
				return true;

			PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_PARSEGENERIC);

			for (i = 0; i < cmd->parms; i++){
				if (!PS_ReadToken(script, &token)){
					Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for utility command in material '%s'\n", material->name);
					break;
				}
			}

			PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

			return true;
		}
	}

	return false;
}

/*
 ==================
 R_ParseGlobalCommand
 ==================
*/
static bool R_ParseGlobalCommand (script_t *script, const char *command, material_t *material){

	materialGlobalCmd_t	*cmd;

	for (cmd = r_materialGlobalCmds; cmd->name; cmd++){
		if (!Str_ICompare(cmd->name, command))
			return cmd->parseCmd(script, material);
	}

	Com_Printf(S_COLOR_YELLOW "WARNING: unknown global command '%s' in material '%s'\n", command, material->name);

	return false;
}

/*
 ==================
 R_ParseStageCommand
 ==================
*/
static bool R_ParseStageCommand (script_t *script, const char *command, material_t *material, stage_t *stage){

	materialStageCmd_t	*cmd;

	for (cmd = r_materialStageCmds; cmd->name; cmd++){
		if (!Str_ICompare(cmd->name, command))
			return cmd->parseCmd(script, material, stage);
	}

	Com_Printf(S_COLOR_YELLOW "WARNING: unknown stage command '%s' in material '%s'\n", command, material->name);

	return false;
}

/*
 ==================
 R_ParseMaterial
 ==================
*/
static bool R_ParseMaterial (script_t *script, material_t *material){

	stage_t	*stage;
	token_t	token;

	// Parse the material
	if (!PS_ExpectTokenString(script, &token, "{")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '{', found '%s' instead in material '%s'\n", token.string, material->name);
		return false;
	}

	while (1){
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: no concluding '}' in material '%s'\n", material->name);
			return false;	// End of data
		}

		if (!Str_Compare(token.string, "}"))
			break;			// End of material

		// Parse a stage
		if (!Str_Compare(token.string, "{")){
			// Create a new stage
			if (material->numStages == MAX_STAGES){
				Com_Printf(S_COLOR_YELLOW "WARNING: MAX_STAGES hit in material '%s'\n", material->name);
				return false;
			}
			stage = &material->stages[material->numStages++];

			// Parse it
			while (1){
				if (!PS_ReadToken(script, &token)){
					Com_Printf(S_COLOR_YELLOW "WARNING: no matching '}' in material '%s'\n", material->name);
					return false;	// End of data
				}

				if (!Str_Compare(token.string, "}"))
					break;			// End of stage

				// Parse the command
				if (!R_ParseStageCommand(script, token.string, material, stage))
					return false;
			}

			continue;
		}

		// Skip utility commands
		if (R_IsUtilityCommand(script, token.string, material))
			continue;

		// Parse the command
		if (!R_ParseGlobalCommand(script, token.string, material))
			return false;
	}

	return true;
}

/*
 ==================
 R_ParseMaterialFile
 ==================
*/
static void R_ParseMaterialFile (script_t *script){

	materialScript_t	*materialScript;
	token_t				token;
	const char			*buffer;
	int					size;
	uint				hashKey;

	while (1){
		// Parse the name
		if (!PS_ReadToken(script, &token))
			break;		// End of data

		// If it's "table", then parse a table
		if (!Str_ICompare(token.string, "table")){
			if (!R_ParseTable(script))
				PS_SkipRestOfLine(script);

			continue;
		}

		// Parse the script
		if (PS_EndOfScript(script))
			break;		// End of data

		buffer = script->text;

		PS_SkipBracedSection(script, 0);

		size = script->text - buffer;

		// Store the material script
		materialScript = (materialScript_t *)Mem_Alloc(sizeof(materialScript_t), TAG_RENDERER);

		Str_Copy(materialScript->name, token.string, sizeof(materialScript->name));

		materialScript->buffer = (char *)Mem_Alloc(size + 1, TAG_RENDERER);
		materialScript->size = size;

		memcpy(materialScript->buffer, buffer, size);
		materialScript->buffer[size] = 0;

		// Add to hash table
		hashKey = Str_HashKey(materialScript->name, MATERIALS_HASH_SIZE, false);

		materialScript->nextHash = r_materialScriptsHashTable[hashKey];
		r_materialScriptsHashTable[hashKey] = materialScript;
	}
}


/*
 ==============================================================================

 MATERIAL LOADING

 ==============================================================================
*/


/*
 ==================
 R_NewMaterial
 ==================
*/
static material_t *R_NewMaterial (void){

	material_t	*material;

	// Clear the material
	material = &r_parseMaterial;
	memset(material, 0, sizeof(r_parseMaterial));

	// Clear the stages
	material->stages = r_parseStages;
	memset(material->stages, 0, sizeof(r_parseStages));

	// Clear the expression ops
	material->expressionOps = r_parseExpressionOps;
	memset(material->expressionOps, 0, sizeof(r_parseExpressionOps));

	// Clear the expression registers
	material->expressionRegisters = r_parseExpressionRegisters;
	memset(material->expressionRegisters, 0, sizeof(r_parseExpressionRegisters));

	// Clear texture flags, filter, and wrap
	memset(r_parseTextureFlags, 0, sizeof(r_parseTextureFlags));
	memset(r_parseTextureFilter, 0, sizeof(r_parseTextureFilter));
	memset(r_parseTextureWrap, 0, sizeof(r_parseTextureWrap));

	return material;
}

/*
 ==================
 R_CreateDefaultMaterial
 ==================
*/
static material_t *R_CreateDefaultMaterial (const char *name, materialType_t type){

	material_t	*material;

	// Create a new material
	material = R_NewMaterial();

	// Fill it in
	Str_Copy(material->name, name, sizeof(material->name));
	material->type = type;
	material->coverage = MC_OPAQUE;
	material->flags = MF_DEFAULTED;
	material->numRegisters = EXP_REGISTER_NUM_PREDEFINED;

	if (type == MT_LIGHT)
		material->stages->textureStage.texture = tr.attenuationTexture;
	else
		material->stages->textureStage.texture = tr.defaultTexture;

	material->numStages++;

	return material;
}

/*
 ==================
 R_CreateMaterial
 ==================
*/
static material_t *R_CreateMaterial (const char *name, materialType_t type, materialScript_t *materialScript){

	material_t	*material;
	script_t	*script;

	// Create a new material
	material = R_NewMaterial();

	// Fill it in
	Str_Copy(material->name, name, sizeof(material->name));
	material->type = type;
	material->coverage = MC_OPAQUE;
	material->flags = 0;
	material->numRegisters = EXP_REGISTER_NUM_PREDEFINED;

	// If we have a script, create an external material
	if (materialScript){
		material->flags |= MF_EXTERNAL;

		// Load the script text
		script = PS_LoadScriptMemory(materialScript->name, materialScript->buffer, materialScript->size);
		if (!script)
			return R_CreateDefaultMaterial(name, type);

		PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

		// Parse it
		if (!R_ParseMaterial(script, material)){
			PS_FreeScript(script);

			return R_CreateDefaultMaterial(name, type);
		}

		PS_FreeScript(script);

		return material;
	}

	// Otherwise create an internal material
	switch (material->type){
	case MT_GENERIC:
		// Bump stage
		material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s_local.tga", material->name), TF_NORMALMAP, TF_DEFAULT, TW_REPEAT);
		if (!material->stages[material->numStages].textureStage.texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture for material '%s', using default...\n", material->name);
			material->stages[material->numStages].textureStage.texture = tr.flatTexture;
		}

		material->stages[material->numStages++].lighting = SL_BUMP;

		// Diffuse stage
		material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s_d.tga", material->name), 0, TF_DEFAULT, TW_REPEAT);
		if (!material->stages[material->numStages].textureStage.texture){
			material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s.tga", material->name), 0, TF_DEFAULT, TW_REPEAT);
			if (!material->stages[material->numStages].textureStage.texture){
				Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture for material '%s', using default...\n", material->name);
				material->stages[material->numStages].textureStage.texture = tr.whiteTexture;
			}
		}

		material->stages[material->numStages++].lighting = SL_DIFFUSE;

		// Specular stage
		material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s_s.tga", material->name), 0, TF_DEFAULT, TW_REPEAT);
		if (!material->stages[material->numStages].textureStage.texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture for material '%s', using default...\n", material->name);
			material->stages[material->numStages].textureStage.texture = tr.blackTexture;
		}

		material->stages[material->numStages++].lighting = SL_SPECULAR;

		break;
	case MT_LIGHT:
		material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s.tga", material->name), TF_NOPICMIP | TF_LIGHTMAP, TF_DEFAULT, TW_CLAMP_TO_ZERO);
		if (!material->stages[material->numStages].textureStage.texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture for material '%s', using default...\n", material->name);
			material->stages[material->numStages].textureStage.texture = tr.attenuationTexture;
		}

		material->stages[material->numStages].colorStage.registers[0] = EXP_REGISTER_PARM0;
		material->stages[material->numStages].colorStage.registers[1] = EXP_REGISTER_PARM1;
		material->stages[material->numStages].colorStage.registers[2] = EXP_REGISTER_PARM2;
		material->stages[material->numStages].colorStage.registers[3] = EXP_REGISTER_PARM3;

		material->numStages++;

		break;
	case MT_NOMIP:
		material->stages[material->numStages].textureStage.texture = R_FindTexture(Str_VarArgs("%s.tga", material->name), TF_NOPICMIP, TF_LINEAR, TW_REPEAT);
		if (!material->stages[material->numStages].textureStage.texture){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find texture for material '%s', using default...\n", material->name);
			material->stages[material->numStages].textureStage.texture = tr.defaultTexture;
		}

		material->stages[material->numStages].flags = SF_BLEND;
		material->stages[material->numStages].blendSrc = GL_SRC_ALPHA;
		material->stages[material->numStages].blendDst = GL_ONE_MINUS_SRC_ALPHA;
		material->stages[material->numStages].colorStage.vertexColor = VC_MODULATE;

		material->numStages++;

		break;
	}

	return material;
}

/*
 ==================
 R_FinishMaterial
 ==================
*/
static void R_FinishMaterial (material_t *material){

	stage_t	*stage;
	int		i, j;

	// Force materials with no stages to use 'noOverlays' and 'noFog'
	if (!material->numStages)
		material->flags |= (MF_NOOVERLAYS | MF_NOFOG);

	// 'ambientLight', 'fogLight', and 'blendLight' imply 'noShadows'
	if (material->lightType != LT_GENERIC)
		material->flags |= MF_NOSHADOWS;

	// Make sure it has a 'lightFalloffImage'
	if (!material->lightFalloffImage){
		if (material->lightType == LT_FOG)
			material->lightFalloffImage = tr.fogEnterTexture;
		else if (material->lightType == LT_AMBIENT)
			material->lightFalloffImage = tr.noFalloffTexture;
		else
			material->lightFalloffImage = tr.falloffTexture;
	}

	// Force 2D materials to use 'twoSided'
	if (material->type == MT_NOMIP)
		material->cullType = CT_TWO_SIDED;

	// 'backSided' and 'twoSided' imply 'noShadows'
	if (material->cullType != CT_FRONT_SIDED)
		material->flags |= MF_NOSHADOWS;

	// 'deform' implies 'noOverlays', 'noFog' and 'noShadows'
	if (material->deform != DFRM_NONE)
		material->flags |= (MF_NOOVERLAYS | MF_NOFOG | MF_NOSHADOWS);

	// Check stages
	for (i = 0, stage = material->stages; i < material->numStages; i++, stage++){
		// Check whether it is a shader or a texture & color stage
		if (stage->shaderStage.program){
			// Make sure it is an ambient stage
			if (stage->lighting != SL_AMBIENT){
				Com_Printf(S_COLOR_YELLOW "WARNING: material '%s' has a non-ambient stage with shaders!\n", material->name);

				stage->lighting = SL_AMBIENT;
			}

			// If 'sort' is unset and the material references _currentRender,
			// force it to use 'sort postProcess'
			if (material->sort == SORT_BAD){
				for (j = 0; j < stage->shaderStage.numShaderMaps; j++){
					if (stage->shaderStage.shaderMaps[j].texture == tr.currentRenderTexture)
						break;
				}

				if (j != stage->shaderStage.numShaderMaps)
					material->sort = SORT_POST_PROCESS;
			}
		}
		else {
			// Make sure it has a texture
			if (!stage->textureStage.texture){
				Com_Printf(S_COLOR_YELLOW "WARNING: material '%s' has a stage with no texture!\n", material->name);

				if (material->type == MT_LIGHT){
					if (material->lightType == LT_FOG)
						stage->textureStage.texture = tr.fogTexture;
					else if (material->lightType == LT_AMBIENT)
						stage->textureStage.texture = tr.noAttenuationTexture;
					else
						stage->textureStage.texture = tr.attenuationTexture;
				}
				else {
					switch (stage->lighting){
					case SL_AMBIENT:
						stage->textureStage.texture = tr.defaultTexture;
						break;
					case SL_BUMP:
						stage->textureStage.texture = tr.flatTexture;
						break;
					case SL_DIFFUSE:
						stage->textureStage.texture = tr.whiteTexture;
						break;
					case SL_SPECULAR:
						stage->textureStage.texture = tr.blackTexture;
						break;
					}
				}
			}

			// If 'sort' is unset and the material references _currentRender,
			// force it to use 'sort postProcess'
			if (material->sort == SORT_BAD){
				if (stage->textureStage.texture == tr.currentRenderTexture)
					material->sort = SORT_POST_PROCESS;
			}
		}

		// Make sure it has a 'specularExponent'
		if (!stage->specularExponent)
			stage->specularExponent = 16.0f;

		// Make sure it has a 'specularBrightness'
		if (!stage->specularBrightness)
			stage->specularBrightness = 2.0f;

		// Check if it may have alpha tested holes
		if (stage->flags & SF_ALPHATEST)
			material->coverage = MC_PERFORATED;

		// Force 2D materials to use 'ignoreAlphaTest'
		if (material->type == MT_NOMIP)
			stage->flags |= SF_IGNOREALPHATEST;

		// Count ambient stages and determine if it has glow stages
		if (stage->lighting == SL_AMBIENT){
			material->numAmbientStages++;

			if (stage->isGlow)
				material->hasGlowStages = true;
		}
	}

	// Force materials with no stages to use 'sort opaque'
	if (!material->numStages)
		material->sort = SORT_OPAQUE;

	// Force materials with lighting stages to use 'sort opaque'
	if (material->numStages != material->numAmbientStages)
		material->sort = SORT_OPAQUE;

	// Set 'sort' if unset
	if (material->sort == SORT_BAD){
		if (material->flags & MF_POLYGONOFFSET)
			material->sort = SORT_DECAL;
		else {
			if (!(material->stages->flags & SF_BLEND) || (material->stages->blendSrc == GL_ONE && material->stages->blendDst == GL_ZERO))
				material->sort = SORT_OPAQUE;
			else {
				if ((material->stages->blendSrc == GL_SRC_ALPHA || material->stages->blendSrc == GL_ONE) && material->stages->blendDst == GL_ONE)
					material->sort = SORT_ADDITIVE;
				else
					material->sort = SORT_MEDIUM;
			}
		}
	}

	// Check if it's translucent
	if (material->sort > SORT_OPAQUE){
		// Set the coverage
		material->coverage = MC_TRANSLUCENT;

		// Force it to use 'noFog'
		material->flags |= MF_NOFOG;

		// Force it to use 'ignoreAlphaTest'
		for (i = 0, stage = material->stages; i < material->numStages; i++, stage++)
			stage->flags |= SF_IGNOREALPHATEST;
	}
}

/*
 ==================
 R_OptimizeMaterial
 ==================
*/
static void R_OptimizeMaterial (material_t *material){

	float	*registers = material->expressionRegisters;
	expOp_t	*op;
	int		a, b;
	int		i;

	// Make sure the predefined registers are initialized
	registers[EXP_REGISTER_IDENTITY] = 1.0f;
	registers[EXP_REGISTER_SHADERPROGRAMS] = 0.0f;
	registers[EXP_REGISTER_SHADERLEVEL] = 0.0f;
	registers[EXP_REGISTER_TIME] = 0.0f;
	registers[EXP_REGISTER_PARM0] = 0.0f;
	registers[EXP_REGISTER_PARM1] = 0.0f;
	registers[EXP_REGISTER_PARM2] = 0.0f;
	registers[EXP_REGISTER_PARM3] = 0.0f;
	registers[EXP_REGISTER_PARM4] = 0.0f;
	registers[EXP_REGISTER_PARM5] = 0.0f;
	registers[EXP_REGISTER_PARM6] = 0.0f;
	registers[EXP_REGISTER_PARM7] = 0.0f;
	registers[EXP_REGISTER_GLOBAL0] = 0.0f;
	registers[EXP_REGISTER_GLOBAL1] = 0.0f;
	registers[EXP_REGISTER_GLOBAL2] = 0.0f;
	registers[EXP_REGISTER_GLOBAL3] = 0.0f;
	registers[EXP_REGISTER_GLOBAL4] = 0.0f;
	registers[EXP_REGISTER_GLOBAL5] = 0.0f;
	registers[EXP_REGISTER_GLOBAL6] = 0.0f;
	registers[EXP_REGISTER_GLOBAL7] = 0.0f;

	// Check for constant expressions
	if (!material->numOps)
		return;

	for (i = 0, op = material->expressionOps; i < material->numOps; i++, op++){
		if (op->opType != OP_TYPE_TABLE){
			if (op->a < EXP_REGISTER_NUM_PREDEFINED || op->b < EXP_REGISTER_NUM_PREDEFINED)
				break;
		}
		else {
			if (op->b < EXP_REGISTER_NUM_PREDEFINED)
				break;
		}
	}

	if (i != material->numOps)
		return;		// Something references a variable

	// Evaluate all the registers
	for (i = 0, op = material->expressionOps; i < material->numOps; i++, op++){
		switch (op->opType){
		case OP_TYPE_MULTIPLY:
			registers[op->c] = registers[op->a] * registers[op->b];
			break;
		case OP_TYPE_DIVIDE:
			if (registers[op->b] == 0.0f){
				Com_Printf(S_COLOR_YELLOW "WARNING: division by zero in material '%s'\n", material->name);

				registers[op->c] = 0.0f;
				break;
			}

			registers[op->c] = registers[op->a] / registers[op->b];
			break;
		case OP_TYPE_MOD:
			a = M_FtolFast(registers[op->a]);
			b = M_FtolFast(registers[op->b]);

			if (b == 0){
				Com_Printf(S_COLOR_YELLOW "WARNING: division by zero in material '%s'\n", material->name);

				registers[op->c] = 0.0f;
				break;
			}

			registers[op->c] = (float)(a % b);
			break;
		case OP_TYPE_ADD:
			registers[op->c] = registers[op->a] + registers[op->b];
			break;
		case OP_TYPE_SUBTRACT:
			registers[op->c] = registers[op->a] - registers[op->b];
			break;
		case OP_TYPE_GREATER:
			registers[op->c] = registers[op->a] > registers[op->b];
			break;
		case OP_TYPE_LESS:
			registers[op->c] = registers[op->a] < registers[op->b];
			break;
		case OP_TYPE_GEQUAL:
			registers[op->c] = registers[op->a] >= registers[op->b];
			break;
		case OP_TYPE_LEQUAL:
			registers[op->c] = registers[op->a] <= registers[op->b];
			break;
		case OP_TYPE_EQUAL:
			registers[op->c] = registers[op->a] == registers[op->b];
			break;
		case OP_TYPE_NOTEQUAL:
			registers[op->c] = registers[op->a] != registers[op->b];
			break;
		case OP_TYPE_AND:
			registers[op->c] = registers[op->a] && registers[op->b];
			break;
		case OP_TYPE_OR:
			registers[op->c] = registers[op->a] || registers[op->b];
			break;
		case OP_TYPE_TABLE:
			registers[op->c] = R_LookupTable(op->a, registers[op->b]);
			break;
		default:
			Com_Printf(S_COLOR_YELLOW "WARNING: invalid expression operator in material '%s'\n", material->name);
			break;
		}
	}

	// We don't need to evaluate the registers during rendering, except for
	// development purposes
	material->constantExpressions = true;
}

/*
 ==================
 R_FixMaterialIndices
 ==================
*/
static void R_FixMaterialIndices (scene_t *scene, int numDrawMeshes, drawMesh_t *drawMeshes, int index){

	drawMesh_t	*drawMesh;
	material_t	*material;
	entity_t	*entity;
	meshType_t	type;
	bool		caps;
	int			sortedIndex;
	int			i;

	if (!numDrawMeshes)
		return;

	for (i = 0, drawMesh = drawMeshes; i < numDrawMeshes; i++, drawMesh++){
		sortedIndex = (drawMesh->sort >> SORT_SHIFT_MATERIAL) & (MAX_MATERIALS-1);
		if (sortedIndex < index)
			continue;

		R_DecomposeSort(scene, drawMesh->sort, &material, &entity, &type, &caps);

		drawMesh->sort = ((sortedIndex + 1) << SORT_SHIFT_MATERIAL) | (entity->index << SORT_SHIFT_ENTITY) | (type << SORT_SHIFT_TYPE) | (caps);
	}
}

/*
 ==================
 R_FixRenderCommandList

 This is a nasty issue. Materials can be registered after render views are
 generated but before the frame is rendered. This will, for the duration of one
 frame, cause render views to be rendered with bad materials. To fix this, we
 need to go through all the render commands and fix the sorted indices.
 ==================
*/
static void R_FixRenderCommandList (int index){

	renderCommandList_t			*commandList = &backEnd.commandList;
	const void					*data;
	const renderViewCommand_t	*renderViewCmd;
	drawLight_t					*drawLight;
	int							i;

	if (!commandList->size)
		return;

	// Add an end of list command to make sure we won't get stuck into an
	// infinite loop
	*(int *)(commandList->data + commandList->size) = RC_END_OF_LIST;

	// Run through the commands
	data = commandList->data;
	while (1){
		switch (*(const int *)data){
		case RC_RENDER_VIEW:
			renderViewCmd = (const renderViewCommand_t *)data;

			if (!renderViewCmd->viewParms.bloom){
				R_FixMaterialIndices(renderViewCmd->scene, renderViewCmd->numDrawMeshes, renderViewCmd->drawMeshes, index);
				R_FixMaterialIndices(renderViewCmd->scene, renderViewCmd->numDrawPostProcessMeshes, renderViewCmd->drawPostProcessMeshes, index);

				for (i = 0, drawLight = renderViewCmd->drawLights; i < renderViewCmd->numDrawLights; i++, drawLight++){
					R_FixMaterialIndices(renderViewCmd->scene, drawLight->numInteractionMeshes[0], drawLight->interactionMeshes[0], index);
					R_FixMaterialIndices(renderViewCmd->scene, drawLight->numInteractionMeshes[1], drawLight->interactionMeshes[1], index);

					R_FixMaterialIndices(renderViewCmd->scene, drawLight->numShadowMeshes[0], drawLight->shadowMeshes[0], index);
					R_FixMaterialIndices(renderViewCmd->scene, drawLight->numShadowMeshes[1], drawLight->shadowMeshes[1], index);
				}

				for (i = 0, drawLight = renderViewCmd->drawFogLights; i < renderViewCmd->numDrawFogLights; i++, drawLight++)
					R_FixMaterialIndices(renderViewCmd->scene, drawLight->numInteractionMeshes[0], drawLight->interactionMeshes[0], index);
			}

			data = (const void *)((const byte *)data + sizeof(renderViewCommand_t));
			break;
		case RC_RENDER_BLOOM:
			data = (const void *)((const byte *)data + sizeof(renderBloomCommand_t));
			break;
		case RC_COMPOSITE_BLOOM:
			data = (const void *)((const byte *)data + sizeof(compositeBloomCommand_t));
			break;
		case RC_CAPTURE_RENDER:
			data = (const void *)((const byte *)data + sizeof(captureRenderCommand_t));
			break;
		case RC_UPDATE_TEXTURE:
			data = (const void *)((const byte *)data + sizeof(updateTextureCommand_t));
			break;
		case RC_SET_COLOR:
			data = (const void *)((const byte *)data + sizeof(setColorCommand_t));
			break;
		case RC_STRETCH_PIC:
			data = (const void *)((const byte *)data + sizeof(stretchPicCommand_t));
			break;
		case RC_SHEARED_PIC:
			data = (const void *)((const byte *)data + sizeof(shearedPicCommand_t));
			break;
		case RC_RENDER_SIZE:
			data = (const void *)((const byte *)data + sizeof(renderSizeCommand_t));
			break;
		case RC_SET_BUFFERS:
			data = (const void *)((const byte *)data + sizeof(setBuffersCommand_t));
			break;
		case RC_SWAP_BUFFERS:
			data = (const void *)((const byte *)data + sizeof(swapBuffersCommand_t));
			break;
		case RC_END_OF_LIST:
			return;
		default:
			Com_Error(true, "R_FixRenderCommandList: bad commandId (%i)", *(const int *)data);
		}
	}
}

/*
 ==================
 R_SortMaterial
 ==================
*/
static void R_SortMaterial (material_t *material){

	int		i;

	for (i = r_numMaterials - 2; i >= 0; i--){
		if (tr.sortedMaterials[i]->sort <= material->sort)
			break;

		tr.sortedMaterials[i+1] = tr.sortedMaterials[i];
		tr.sortedMaterials[i+1]->index++;
	}

	R_FixRenderCommandList(i+1);

	material->index = i+1;
	tr.sortedMaterials[i+1] = material;
}

/*
 ==================
 R_LoadMaterial
 ==================
*/
static material_t *R_LoadMaterial (material_t *newMaterial){

	material_t	*material;
	uint		hashKey;

	if (r_numMaterials == MAX_MATERIALS)
		Com_Error(false, "R_LoadMaterial: MAX_MATERIALS hit");

	r_materials[r_numMaterials++] = material = (material_t *)Mem_Alloc(sizeof(material_t), TAG_RENDERER);

	// Copy the material
	memcpy(material, newMaterial, sizeof(material_t));

	// Allocate and copy the stages
	material->stages = (stage_t *)Mem_Alloc(material->numStages * sizeof(stage_t), TAG_RENDERER);
	memcpy(material->stages, newMaterial->stages, material->numStages * sizeof(stage_t));

	// Allocate and copy the expression ops
	material->expressionOps = (expOp_t *)Mem_Alloc(material->numOps * sizeof(expOp_t), TAG_RENDERER);
	memcpy(material->expressionOps, newMaterial->expressionOps, material->numOps * sizeof(expOp_t));

	// Allocate and copy the expression registers
	material->expressionRegisters = (float *)Mem_Alloc(material->numRegisters * sizeof(float), TAG_RENDERER);
	memcpy(material->expressionRegisters, newMaterial->expressionRegisters, material->numRegisters * sizeof(float));

	// Make sure all the parameters are valid
	R_FinishMaterial(material);

	// Check for constant expressions
	R_OptimizeMaterial(material);

	// Sort the material
	R_SortMaterial(material);

	// Add to hash table
	hashKey = Str_HashKey(material->name, MATERIALS_HASH_SIZE, false);

	material->nextHash = r_materialsHashTable[hashKey];
	r_materialsHashTable[hashKey] = material;

	return material;
}

/*
 ==================
 R_FindMaterial
 ==================
*/
material_t *R_FindMaterial (const char *name, materialType_t type){

	material_t			*material;
	materialScript_t	*materialScript;
	uint				hashKey;

	// Performance evaluation option
	if (r_singleMaterial->integerValue && type == MT_GENERIC)
		return tr.defaultMaterial;

	// See if already loaded
	hashKey = Str_HashKey(name, MATERIALS_HASH_SIZE, false);

	for (material = r_materialsHashTable[hashKey]; material; material = material->nextHash){
		if (material->type != type)
			continue;

		if (!Str_ICompare(material->name, name))
			return material;
	}

	// See if there's a script for this material
	for (materialScript = r_materialScriptsHashTable[hashKey]; materialScript; materialScript = materialScript->nextHash){
		if (!Str_ICompare(materialScript->name, name))
			break;
	}

	// Create the material
	material = R_CreateMaterial(name, type, materialScript);

	// Load it in
	return R_LoadMaterial(material);
}

/*
 ==================
 R_RegisterMaterial
 ==================
*/
material_t *R_RegisterMaterial (const char *name){

	return R_FindMaterial(name, MT_GENERIC);
}

/*
 ==================
 R_RegisterMaterialLight
 ==================
*/
material_t *R_RegisterMaterialLight (const char *name){

	return R_FindMaterial(name, MT_LIGHT);
}

/*
 ==================
 R_RegisterMaterialNoMip
 ==================
*/
material_t *R_RegisterMaterialNoMip (const char *name){

	return R_FindMaterial(name, MT_NOMIP);
}


// ============================================================================


/*
 ==================
 R_EvaluateRegisters
 ==================
*/
void R_EvaluateRegisters (material_t *material, float time, const float *entityParms, const float *globalParms){

	float	*registers = material->expressionRegisters;
	expOp_t	*op;
	int		a, b;
	int		i;

	if (r_skipExpressions->integerValue)
		return;

	if (material->constantExpressions && !r_skipConstantExpressions->integerValue)
		return;

	// Update the predefined registers
	registers[EXP_REGISTER_IDENTITY] = 1.0f;
	registers[EXP_REGISTER_SHADERPROGRAMS] = M_ClampLong(r_shaderPrograms->integerValue, 0, 1);
	registers[EXP_REGISTER_SHADERLEVEL] = M_ClampLong(r_shaderDetailLevel->integerValue, 0, 2);
	registers[EXP_REGISTER_TIME] = time;
	registers[EXP_REGISTER_PARM0] = entityParms[0];
	registers[EXP_REGISTER_PARM1] = entityParms[1];
	registers[EXP_REGISTER_PARM2] = entityParms[2];
	registers[EXP_REGISTER_PARM3] = entityParms[3];
	registers[EXP_REGISTER_PARM4] = entityParms[4];
	registers[EXP_REGISTER_PARM5] = entityParms[5];
	registers[EXP_REGISTER_PARM6] = entityParms[6];
	registers[EXP_REGISTER_PARM7] = entityParms[7];
	registers[EXP_REGISTER_GLOBAL0] = globalParms[0];
	registers[EXP_REGISTER_GLOBAL1] = globalParms[1];
	registers[EXP_REGISTER_GLOBAL2] = globalParms[2];
	registers[EXP_REGISTER_GLOBAL3] = globalParms[3];
	registers[EXP_REGISTER_GLOBAL4] = globalParms[4];
	registers[EXP_REGISTER_GLOBAL5] = globalParms[5];
	registers[EXP_REGISTER_GLOBAL6] = globalParms[6];
	registers[EXP_REGISTER_GLOBAL7] = globalParms[7];

	// Evaluate all the registers
	for (i = 0, op = material->expressionOps; i < material->numOps; i++, op++){
		switch (op->opType){
		case OP_TYPE_MULTIPLY:
			registers[op->c] = registers[op->a] * registers[op->b];
			break;
		case OP_TYPE_DIVIDE:
			if (registers[op->b] == 0.0f){
				Com_Printf(S_COLOR_YELLOW "R_EvaluateRegisters: division by zero in material '%s'\n", material->name);

				registers[op->c] = 0.0f;
				break;
			}

			registers[op->c] = registers[op->a] / registers[op->b];
			break;
		case OP_TYPE_MOD:
			a = M_FtolFast(registers[op->a]);
			b = M_FtolFast(registers[op->b]);

			if (b == 0){
				Com_Printf(S_COLOR_YELLOW "R_EvaluateRegisters: division by zero in material '%s'\n", material->name);

				registers[op->c] = 0.0f;
				break;
			}

			registers[op->c] = (float)(a % b);
			break;
		case OP_TYPE_ADD:
			registers[op->c] = registers[op->a] + registers[op->b];
			break;
		case OP_TYPE_SUBTRACT:
			registers[op->c] = registers[op->a] - registers[op->b];
			break;
		case OP_TYPE_GREATER:
			registers[op->c] = registers[op->a] > registers[op->b];
			break;
		case OP_TYPE_LESS:
			registers[op->c] = registers[op->a] < registers[op->b];
			break;
		case OP_TYPE_GEQUAL:
			registers[op->c] = registers[op->a] >= registers[op->b];
			break;
		case OP_TYPE_LEQUAL:
			registers[op->c] = registers[op->a] <= registers[op->b];
			break;
		case OP_TYPE_EQUAL:
			registers[op->c] = registers[op->a] == registers[op->b];
			break;
		case OP_TYPE_NOTEQUAL:
			registers[op->c] = registers[op->a] != registers[op->b];
			break;
		case OP_TYPE_AND:
			registers[op->c] = registers[op->a] && registers[op->b];
			break;
		case OP_TYPE_OR:
			registers[op->c] = registers[op->a] || registers[op->b];
			break;
		case OP_TYPE_TABLE:
			registers[op->c] = R_LookupTable(op->a, registers[op->b]);
			break;
		default:
			Com_Error(false, "R_EvaluateRegisters: bad opType (%i)", op->opType);
		}
	}
}

/*
 ==================
 R_EnumMaterialScripts

 This is for use by integrated editors
 ==================
*/
void R_EnumMaterialScripts (void (*callback)(const char *)){

	materialScript_t	*materialScript;
	int					i;

	for (i = 0; i < MATERIALS_HASH_SIZE; i++){
		materialScript = r_materialScriptsHashTable[i];

		while (materialScript){
			callback(materialScript->name);

			materialScript = materialScript->nextHash;
		}
	}
}

/*
 ==================
 R_ListMaterials_f
 ==================
*/
static void R_ListMaterials_f (void){

	material_t	*material;
	int			i;

	Com_Printf("\n");
	Com_Printf("      -stg -amb -ops -reg sort src -type-- -name--------\n");

	for (i = 0; i < r_numMaterials; i++){
		material = r_materials[i];

		Com_Printf("%4i: ", i);

		Com_Printf("%4i %4i ", material->numStages, material->numAmbientStages);

		Com_Printf("%4i %4i ", material->numOps, material->numRegisters);

		Com_Printf("%4i ", material->sort);

		if (material->flags & MF_EXTERNAL)
			Com_Printf(" E  ");
		else
			Com_Printf(" I  ");

		switch (material->type){
		case MT_GENERIC:
			Com_Printf("GENERIC ");
			break;
		case MT_LIGHT:
			Com_Printf("LIGHT   ");
			break;
		case MT_NOMIP:
			Com_Printf("NOMIP   ");
			break;
		default:
			Com_Printf("??????? ");
			break;
		}

		Com_Printf("%s%s\n", material->name, (material->flags & MF_DEFAULTED) ? " (DEFAULTED)" : "");
	}

	Com_Printf("--------------------------------------------------------\n");
	Com_Printf("%i total materials\n", r_numMaterials);
	Com_Printf("\n");
}

/*
 ==================
 R_CreateBuiltInMaterials
 ==================
*/
static void R_CreateBuiltInMaterials (void){

	material_t	*material;

	// Default material
	material = R_NewMaterial();

	Str_Copy(material->name, "_default", sizeof(material->name));
	material->type = MT_GENERIC;
	material->coverage = MC_OPAQUE;
	material->flags = MF_NOOVERLAYS | MF_NOFOG | MF_NOSHADOWS;
	material->numRegisters = EXP_REGISTER_NUM_PREDEFINED;
	material->stages->textureStage.texture = tr.defaultTexture;
	material->stages->colorStage.vertexColor = VC_MODULATE;
	material->numStages++;

	tr.defaultMaterial = R_LoadMaterial(material);

	// Default point light material
	material = R_NewMaterial();

	Str_Copy(material->name, "_defaultPointLight", sizeof(material->name));
	material->type = MT_LIGHT;
	material->coverage = MC_OPAQUE;
	material->flags = 0;
	material->numRegisters = EXP_REGISTER_NUM_PREDEFINED;
	material->lightType = LT_GENERIC;
	material->lightFalloffImage = tr.falloffTexture;
	material->stages->textureStage.texture = tr.attenuationTexture;
	material->stages->colorStage.registers[0] = EXP_REGISTER_PARM0;
	material->stages->colorStage.registers[1] = EXP_REGISTER_PARM1;
	material->stages->colorStage.registers[2] = EXP_REGISTER_PARM2;
	material->stages->colorStage.registers[3] = EXP_REGISTER_PARM3;
	material->numStages++;

	tr.defaultPointLightMaterial = R_LoadMaterial(material);

	// Default projected light material
	material = R_NewMaterial();

	Str_Copy(material->name, "_defaultProjectedLight", sizeof(material->name));
	material->type = MT_LIGHT;
	material->coverage = MC_OPAQUE;
	material->flags = 0;
	material->numRegisters = EXP_REGISTER_NUM_PREDEFINED;
	material->lightType = LT_GENERIC;
	material->lightFalloffImage = tr.noFalloffTexture;
	material->stages->textureStage.texture = tr.attenuationTexture;
	material->stages->colorStage.registers[0] = EXP_REGISTER_PARM0;
	material->stages->colorStage.registers[1] = EXP_REGISTER_PARM1;
	material->stages->colorStage.registers[2] = EXP_REGISTER_PARM2;
	material->stages->colorStage.registers[3] = EXP_REGISTER_PARM3;
	material->numStages++;

	tr.defaultProjectedLightMaterial = R_LoadMaterial(material);
}

/*
 ==================
 R_InitMaterials
 ==================
*/
void R_InitMaterials (void){

	script_t	*script;
	char		name[MAX_OSPATH];
	const char	**fileList;
	int			numFiles;
	int			i;

	Com_Printf("Initializing Materials\n");

	// Register our commands
	Cmd_AddCommand("listMaterials", R_ListMaterials_f, "Lists loaded materials");

	// Load and parse .mtr files
	fileList = FS_ListFiles("materials", ".mtr", true, &numFiles);

	for (i = 0; i < numFiles; i++){
		// Load the script file
		Str_SPrintf(name, sizeof(name), "materials/%s", fileList[i]);
		Com_Printf("...loading '%s'", name);

		script = PS_LoadScriptFile(name);
		if (!script){
			Com_Printf(": failed\n");
			continue;
		}
		Com_Printf("\n");

		PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

		// Parse it
		R_ParseMaterialFile(script);

		PS_FreeScript(script);
	}

	FS_FreeFileList(fileList);

	// Create built-in materials
	R_CreateBuiltInMaterials();
}

/*
 ==================
 R_ShutdownMaterials
 ==================
*/
void R_ShutdownMaterials (void){

	// Unregister our commands
	Cmd_RemoveCommand("listMaterials");

	// Clear material script and material lists
	memset(r_materialScriptsHashTable, 0, sizeof(r_materialScriptsHashTable));

	memset(r_materialsHashTable, 0, sizeof(r_materialsHashTable));
	memset(r_materials, 0, sizeof(r_materials));

	r_numMaterials = 0;
}
